TypeScript SDKGithubEdit on GitHub

Error handling

The typed error hierarchy — ApiError, AuthError, BillingError, RequestTooLargeError — and how to catch, branch on, and format them for the UI.

Every REST call made through createKortix(...) or backendApi rejects with a real Error subclass — never a plain object. catch it, instanceof it, and branch on .status/.code. These are the same classes on every host: a server-side "Kortix as a backend" wrapper and the React UI both import from the one module.

import { ApiError, BillingError } from '@kortix/sdk';

try {
  await kortix.project(projectId).sessions.create();
} catch (err) {
  if (err instanceof BillingError) {
    // 402 — out of credits / plan limit
  } else if (err instanceof ApiError) {
    // any other failed request — err.status, err.code, err.detail
  } else {
    throw err;
  }
}

The classes

classextendswhenkey fields
ApiErrorErrorthe default for any failed request — bad status, network failure, timeout, or an aborted requeststatus, code, details/data, detail, response, url, endpoint, timeout
AuthErrorApiErrorgetToken returned null — the request was never sentcode is always 'NO_SESSION'
BillingErrorErrorHTTP 402 — the backend's only billing error todaystatus (402), detail: { message, ... }
RequestTooLargeErrorErrorHTTP 431 — typically too many files attached to one requeststatus (431), detail: { message, suggestion }

BillingError used to be an 8-class hierarchy (AGENT_RUN_LIMIT_EXCEEDED, THREAD_LIMIT_EXCEEDED, etc.). The backend now only ever returns a plain 402 with { message }, so it's one class — legacy code that still switches on old error-code strings can delete that branch.

ApiError

The class every failed backendApi/facade call produces. name defaults to 'ApiError' but is overridden for specific failure modes you can match on:

  • name: 'AbortError', code: 'ABORTED' — the request was aborted externally (navigation, React Query cancellation) — not a timeout, safe to ignore.
  • code: 'TIMEOUT' — the request's own timeout budget elapsed; url, endpoint, and timeout (ms) are set so you know what timed out.
  • Otherwise status is the HTTP status code and code is the backend's error_code/detail.error_code (falling back to the status as a string) when the body carried one.

message is an enumerable own property (not just inherited from Error), so spreading the error or JSON.stringify-logging it includes the message — useful for error reporting.

AuthError

Thrown client-side, before any request goes out, when getToken() resolves to null — there's no way to sign the request. It's an ApiError subclass, so err instanceof ApiError still matches; check err instanceof AuthError (or err.code === 'NO_SESSION') to special-case "not signed in" from a real backend failure.

BillingError

Thrown for HTTP 402 responses — out of credits, over a plan limit, or any other billing gate. detail.message is the human-readable reason from the backend; detail may carry additional fields the backend chose to include.

RequestTooLargeError

Thrown for HTTP 431 (Request Header Fields Too Large) — in practice, this means too many files were attached to a single request. detail.suggestion is a ready-to-show hint ("Try uploading files one at a time...").

Helpers

helpersignaturewhat
parseBillingError(error)(error: any) => ErrorIf error's status is 402, wraps it into a BillingError; otherwise returns error unchanged. Called internally on every 402 response, so callers rarely need it directly.
isBillingError(error)(error: any) => booleanerror instanceof BillingError
formatBillingErrorForUI(error)(error: any) => BillingErrorUI | nullReturns null for anything that isn't a BillingError; otherwise returns { alertTitle, alertSubtitle } ready to render in an upgrade/pricing modal — with a dedicated "you ran out of credits" copy when the message mentions credits/balance/insufficient.
import { formatBillingErrorForUI } from '@kortix/sdk';

try {
  await kortix.session(projectId, sessionId).start();
} catch (err) {
  const ui = formatBillingErrorForUI(err);
  if (ui) showUpgradeModal(ui.alertTitle, ui.alertSubtitle);
}

In @kortix/sdk/react

@kortix/sdk/react re-exports BillingError, RequestTooLargeError, parseBillingError, isBillingError, and formatBillingErrorForUInot ApiError/AuthError, which stay root-only (@kortix/sdk) since they're the generic REST-failure shape, not something a chat UI branches on. Import both from wherever you already import @kortix/sdk/@kortix/sdk/react — same classes either way.

useSession's send/answerQuestion/answerPermission/rejectQuestion classify every failure into a KortixSendError (sendError on the hook's return value) instead of making you instanceof-check by hand:

interface KortixSendError {
  kind: 'billing' | 'runtime-not-ready' | 'runtime-error';
  message: string;       // already formatted for display
  billing?: BillingError; // present when kind === 'billing'
  cause: unknown;         // the original thrown value
}
const s = useSession(projectId, sessionId);

if (s.sendError?.kind === 'billing') {
  const ui = formatBillingErrorForUI(s.sendError.billing);
  // ...
}

See React hooks for the rest of useSession's surface.

Error handling – Kortix Docs