Skip to content
GitHub

Web SDK errors

What the Runbear chat widget does on each API error, how to recover from each sessionFailed reason, rate limits, and the RunbearApiError fields.


On this page

The widget handles most failures itself: it renews an expired pass, retries your session endpoint with backoff, and shows a short error message when a reply fails. This page covers what it does on each status, which failures your page hears about, and how to recover.

What your page can observe#

Your page hears about session failures through the sessionFailed event. It does not hear about a failed message: when a reply fails, the widget shows a generic "An unknown error occurred" message to the visitor and logs the error to the browser console, and there is no error event for the host. To watch for failed turns, check the console while you develop, or read the conversation's server-side history.

Session failures#

In session mode, sessionFailed { reason } means the session has ended. It is terminal: the SDK never retries by itself, or the renewal loop the circuit breaker exists to stop would come straight back. Recovery is yours.

reasonWhat happenedStored sessionHow to recover
declinedYour fetchSessionToken threw SessionDeclinedError, or Runbear refused the pass: a 401 that renewal can't heal, a 403, or a 429 with a lifetime-cap codeClearedSign the visitor in again if that was the cause, then call retrySession(), which starts a new conversation
expiredThe session's conversation is gone: a 404 on a session-authorized routeClearedCall retrySession() to start a new conversation
networkYour endpoint failed on 5 attempts in a row (in 0.5.0 and later, not yet released, after one more round without a stale resume token; see Declining a session)KeptCall retrySession(), which resumes the same conversation once your endpoint works again
misconfiguredYour endpoint returned something the SDK can't use as a credential, such as Runbear's response body unmapped or an expiresIn under 10 secondsClearedFix the endpoint's mapping; retrySession() then starts a new conversation
chat.on("sessionFailed", ({ reason }) => {
  if (reason === "network") showRetryButton(() => chat.retrySession())
  else if (reason === "declined") showSignInAgain()
  else chat.retrySession()
})

retrySession() is the only way out of the failed state: reset() and startWithMessage() don't clear it. It does nothing outside session mode or when the session hasn't failed.

Errors and renewal#

The SDK handles renewal for you; this is what it does with each status in session mode.

StatusMeaningWhat happens
401 with code: "pass_expired"The pass aged out.The SDK calls your fetchSessionToken again with the stored resume token. It replays the request once only if it was a read (GET, such as loading the history). A 401 on sending a message (POST …/runs/stream) renews the pass, but that message fails with an error and the visitor's next send succeeds. The SDK renews ahead of expiry, so this is rare.
401 with no codeAn older API build that predates the code vocabulary.Treated exactly like pass_expired: one bounded renewal and at most one replay, never a loop.
401 with any other codeThe credential isn't usable.Terminal. The SDK clears stored credentials and emits sessionFailed { reason: "declined" }.
403The pass isn't allowed to do this.Terminal, reason: "declined". Renewing can't change an authorization decision.
404 on a session-authorized routeThe bound thread is gone, or the request named a different one.Terminal, reason: "expired". The SDK does not silently start a new conversation.
404 from POST /v1/sessionsThe assistant_id doesn't name an agent your organization owns, or a thread_id you passed doesn't belong to that agent. Runbear answers the same way for both, and for an agent that doesn't exist.Your endpoint sees this. Check the agent id first; it is the usual cause.
429Rate-limited.A 429 with code: "rate_limited" is transient: the message fails with the widget's error message, and the SDK does not retry it automatically. A 429 with code: "session_turn_cap_exhausted" or "session_refresh_cap_exhausted" means the session used up its lifetime allowance, and is terminal (reason: "declined"): start a new session. See Session limits for the numbers, and Errors and limits for the organization-wide limits and the rate-limit headers.
5xxA server error.The message fails with the widget's error message. The session is not affected.

What your endpoint's failures do depends on what fetchSessionToken throws: SessionDeclinedError ends the session at once (reason: "declined"); any other error is retried and ends with reason: "network". See Declining a session.

Errors the SDK throws#

These are thrown synchronously, so they surface in your own code:

WhereMessageCause
new Runbear()"Runbear: apiKey must not be set in session mode…"A top-level apiKey together with auth: { mode: "session" }
new Runbear()"Runbear: missing apiKey…"No auth, no apiKey and no baseUrl
chat.mount()"Could not find element to mount chat widget"The selector matched nothing
chat.mount()"Chat widget is already mounted"mount() was called twice on one chat

Console warnings#

The widget logs a warning, prefixed Runbear:, for problems it can work around:

  • its stylesheet is missing, usually a Content-Security-Policy without style-src 'unsafe-inline' (see Install);
  • the mount element wasn't empty (0.5.0 and later, not yet released);
  • auth was passed together with the legacy apiKey or baseUrl options;
  • config.suggestions.enabled is set in session mode;
  • browser storage is unavailable, or another widget already uses the same sessionKey, so the session won't survive a reload;
  • a message was sent before the widget had a conversation;
  • a response component failed to render and its fallbackText is shown instead.

RunbearApiError#

RunbearApiError is the error the SDK's API client rejects with for every non-2xx response. The widget logs it to the console when a message fails. It extends Error and adds:

FieldTypeNotes
statusnumberThe HTTP status
codestring or undefinedThe API's machine-readable code, when the response had one. See Errors and limits
bodystring or undefinedThe response body as text. Untrusted server text: branch on status and code, and never show body or message to visitors
retryAfterSecondsnumber or undefinedThe Retry-After header, when it was a whole number of seconds. The SDK never retries on it

Which fields are filled depends on the request:

Requestcodebody
JSON requests: creating a thread, reading a transcript, suggestionsSet when the body has oneSet
Sending a message (the streaming run)Set for 401 and 429 onlyAlways undefined
File uploadsAlways undefinedAlways undefined

retryAfterSeconds is set on all three whenever the server sent a whole-number Retry-After. RunbearApiError isn't the only error on these paths: an aborted file upload rejects with the browser's DOMException, and an upload whose 2xx response isn't JSON rejects with a plain Error. A streaming run whose successful response has no body rejects with a RunbearApiError carrying that success status.

The client that produces these errors, runbear.api.v1.*, is internal to the widget and isn't a supported public API. To call Runbear yourself, use the REST API from your server.