Skip to content
GitHub

Errors and limits

The REST API's error shapes and codes, which errors to retry, rate limits and their headers, status codes, pagination, and CORS.


On this page

This page is the reference for what the REST API sends back when something goes wrong, and for the limits that apply to every integration. Use it to decide, from the status and code alone, whether to retry, fix the request, or stop.

Error shape#

Most errors share one envelope:

{
  "statusCode": 403,
  "code": "forbidden_by_agent_allowlist",
  "error": "forbidden",
  "message": "This API key is not permitted to access the specified agent."
}
  • statusCode repeats the HTTP status.
  • code is the machine-readable cause, when there is one. Branch on it.
  • error names the status class: bad_request (400), unauthorized (401), forbidden (403), not_found (404), conflict (409) or too_many_requests (429).
  • message is written for a person. Don't parse it.

A body that fails schema validation answers 400 with "code": "FST_ERR_VALIDATION" and a message naming the invalid field.

A server error hides its cause:

{
  "statusCode": 500,
  "error": "Internal Server Error",
  "message": "An internal error occurred. Please try again later."
}

Other shapes#

A few responses use a route-specific body with no statusCode or code. The specific cause is in error instead:

WhereBody
POST /v1/sessions and /v1/sessions/refresh, 404{ "error": "agent_not_found" | "thread_not_found", "message" }
Agent and assistant create and update, 400{ "error": "<cause>", "message" }, for example agent_limit_reached, system_prompt_too_long or provider_type_mismatch
Chat and run routes, 422{ "error": "unprocessable_entity", "message" }

Read code when it is present and fall back to error.

Status codes#

StatusMeaningRetry?
400The request is invalidNo. Fix the request
401The credential is missing, invalid or expiredOnly pass_expired, once, after renewing. See authentication errors
403The credential isn't allowed to do thisNo
404The resource doesn't exist, or isn't visible to your organizationNo
409The request conflicts with the resource's current stateNo, until the state changes
422The request is valid but this agent can't process it. See ChatNo
429A rate limit or quota was reachedYes, after Retry-After seconds, unless code is a lifetime cap
500 and aboveSomething failed on Runbear's sideYes, with backoff

A 404 for a thread is identical whether the thread doesn't exist or belongs to another organization.

403 codes#

Every 403 is terminal: retrying, or renewing the credential, won't change the answer.

codeCause
forbidden_by_key_roleThe API key lacks the capability this operation requires
forbidden_by_agent_allowlistThe agent is outside the API key's allowlist
forbidden_org_endpoint_for_scoped_keyAn API key with an agent allowlist called an organization-level endpoint
forbidden_for_session_passA Web SDK session pass called a route it can't use
forbidden_agent_not_in_passA session pass named an agent other than its own
forbidden_trace_access_for_hipaa_agentThe agent uses HIPAA data handling, which disables trace access
forbidden_ai_gateway_not_permittedThe organization doesn't have the AI Gateway feature
forbidden_google_drive_saThe key's user may not use this Google Drive service account
forbidden_service_account_manageThe key's user may not manage this Google Drive service account

See Authentication and API keys for how the first three are decided. Stream errors, which arrive inside a 200, are listed on Streaming.

Rate limits#

LimitCounted perApplies to
600 requests per 60 secondsOrganization and agentPOST /v1/threads, POST /v1/threads/{threadId}/runs and /runs/stream, POST /v1/chat/completions, POST /v1/chat/suggestions, POST /v1/files/upload
600 requests per 60 secondsOrganization and agentCreating a Web SDK session, POST /v1/sessions
600 requests per 60 secondsOrganizationRenewing a Web SDK session, POST /v1/sessions/refresh
60 requests per 60 secondsWeb SDK sessionEvery request made with a session pass
100 turnsWeb SDK session, for its whole lifeRuns made with a session pass
240 renewalsWeb SDK session, for its whole lifePOST /v1/sessions/refresh
30 requests per minuteOrganization, per API serverGET /v1/agents/{id}/traces and GET /v1/agents/{id}/messages/{messageId}/trace, together
20 requests per minuteOrganization, per API serverGET /v1/agents/{id}/traces/export
  • The chat limit is shared by everyone who talks to the agent through your organization, including every visitor of a Web SDK widget for that agent. Contact support@runbear.io before a high-traffic launch.
  • A run made with a Web SDK session pass counts against both the chat limit and the session's own limits. See Web SDK session limits for what the widget does when one is reached.
  • The trace limits are counted on each API server separately, so the budget you observe can be higher than the number above. Pace yourself by the headers rather than by a fixed count.
  • Routes not listed here have no request limit of their own. API-key validation can still be throttled under heavy load; that answers 429 with Retry-After like any other limit. Until a pending API fix ships, a throttled validation answers 401 api_key_invalid instead, so a sudden 401 for a key that worked moments ago is worth one delayed retry.

Rate-limit headers#

Responses from rate-limited routes carry these headers on every response, successful or not:

HeaderValue
RateLimit-LimitThe size of the budget
RateLimit-RemainingWhat is left of it
RateLimit-ResetSeconds until it resets
Retry-AfterOn a 429 only: seconds to wait before retrying, at most 3600

429 responses#

{
  "statusCode": 429,
  "code": "rate_limited",
  "error": "too_many_requests",
  "message": "Too many requests. Retry after the window resets."
}
codeMeaningWhat to do
rate_limitedA per-window limit was reachedWait Retry-After seconds and retry
session_turn_cap_exhaustedThe Web SDK session used all its turnsStart a new session
session_refresh_cap_exhaustedThe Web SDK session can't be renewed againStart a new session

Trace 429s carry no code; wait Retry-After seconds. Treat any other 429 the same way.

Success status codes#

Most successful calls answer 200. These differ:

OperationStatus
POST /v1/threads200
POST /v1/sessions201
POST /v1/sessions/refresh200
POST /v1/api-keys201
POST /v1/agents201
POST /v1/agents/{id}/tool-integrations201
POST /v1/integrations/google-drive/service-accounts201
GET /v1/ai-gateway204 when no gateway is configured
DELETE /v1/agents/{id}/hooks and DELETE /v1/agents/{id}/session-start-hook204

Pagination#

ListStylePage size
GET /v1/threadscursor, returns nextCursorDefault 100, max 100
GET /v1/credits/usage/periodscursor, returns nextCursorDefault 12, max 24
GET /v1/credits/usage/entriescursor, returns nextCursorDefault 500, max 1000
GET /v1/agents/{id}/tracespage and limitDefault 50, max 100
GET /v1/agents/{id}/traces/exportcursor, returns meta.cursorVaries: pages hold whole traces
GET /v1/threads/{threadId}/messagesNoneThe newest 200 messages, with truncated

Cursors are opaque. Pass back exactly what you received, and stop when it is null. On threads and credits, a cursor the API didn't issue is refused with 400.

CORS#

The API can be called from a browser on any origin:

  • Access-Control-Allow-Origin: *, without credentials. Authenticate with the Authorization header, never cookies.
  • Allowed request headers: content-type, authorization, connection, cache-control and x-requested-with.
  • Readable response headers: RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, Retry-After and x-runbear-thread-id (the last is still rolling out; see Chat).
  • Methods: GET, POST, PUT, DELETE, PATCH, HEAD and OPTIONS.

Calling the API from a browser with an API key exposes the key. Use Web SDK session passes for browser traffic.