Errors
Every non-2xx response is application/problem+json (RFC 9457).
One shape, on every endpoint:
Code
| Field | |
|---|---|
type | URI identifying the kind of error. |
title | Short summary of the kind. Prose; do not branch on it. |
status | The HTTP status, repeated in the body. |
detail | What went wrong with this request. Prose. |
instance | The path that produced it. |
code | Branch on this. A value from the closed list below. |
errorId | Correlates with our logs. Quote it when asking us about a request. |
Branch on code
code comes from a fixed vocabulary, and the list is enforced closed: a code minted by
a provider we call (a payment processor, a telephony provider) never appears here. It
travels in upstreamCode instead, so code always means something we defined.
Adding a code is a contract change. Removing or renaming one is a breaking change and is subject to the deprecation policy.
Codes by status
| Status | Default code | Means |
|---|---|---|
| 400 | bad_request | Malformed request. |
| 401 | unauthorized | Missing or invalid credentials. |
| 402 | payment_required | Billing action needed. |
| 403 | forbidden | Authenticated, not permitted. |
| 404 | resource_not_found | No such resource, or not visible to you. |
| 409 | conflict | Conflicts with current state. |
| 410 | resource_gone | Existed, deliberately removed. |
| 413 | payload_too_large | Body over the limit (see below). |
| 415 | unsupported_media_type | Send JSON. |
| 422 | validation_failed | Failed validation; see invalidFields. |
| 429 | rate_limited | Slow down. See rate limits. |
| 500 | internal_error | Our fault. errorId is the thing to quote. |
| 502 | upstream_error | A service we depend on failed. |
| 503 | service_unavailable | Temporary. Check status, then retry. |
Several codes are narrower than their status, because each calls for a different response:
-
license_required(403): your workspace does not have this module, and a permission change will not fix it. The response names the module infeature, so you can match on that rather than parse the message:CodeSee plans and modules for what each module covers.
-
insufficient_credits(403): the workspace is out of credits. Top up rather than retry. -
maintenance(503): planned downtime, as againstservice_unavailablefor an unplanned failure. Every endpoint answers this for the length of the window,detailcarries the message written for it, andRetry-Afterestimates how long it has left. Nothing you sent was applied, so the request is safe to replay in full. See maintenance windows. -
business_rule(varies): a deliberate refusal rather than a fault. Thedetailexplains it and is written to be shown to a person.
The rest of the vocabulary
The remaining codes are narrower still. Each names a situation a client can do something specific about, and most appear on one area of the API only.
| Code | Status | Where | Means |
|---|---|---|---|
daily_cap_reached | 429 | Chat widgets | The widget's daily credit cap for this workspace is spent. Resets with the day. |
session_limit_reached | 429 | Chat widgets | This visitor has started as many widget sessions today as the widget allows. |
session_expired | 401 | Chat widgets | The widget session token is no longer valid. Start a new session. |
assistant_busy | 409 | Chat widgets | This conversation already has a reply in flight. Wait for it rather than sending again. |
server_busy | 429 | Chat widgets | The shared AI queue is saturated. Back off and retry. |
turnstile_failed | 403 | Chat widgets | The bot check did not pass. |
agent_not_public | 410 | Chat widgets | The agent behind the widget is no longer published. |
origin_not_allowed | 403 | Browser calls | The request came from a web origin that is not on the workspace's allowlist. |
design_locked | 403 | Designs | The design is locked and cannot be edited until it is unlocked. |
design_is_template | 403 | Designs | The operation applies to a design, and this one is a template. Create a design from it first. |
template_missing | 400 | Designs | The request needs a template and named none. |
template_gone | 404 | Designs | The template the request named no longer exists. |
fields_missing | 400 | Designs | The template declares no fields the request could fill. |
text_fields_missing | 400 | Designs | The template declares no text fields the request could write into. |
export_too_large | 422 | Designs | The export exceeds the pixel budget. Lower the resolution or the size. |
ai_generation_failed | 502 | Generation | The model produced no usable result. Retrying is reasonable; the request itself was fine. |
file_not_recoverable | 409 | Files | The file is past the point where it can be restored from the trash. |
db_pool_timeout | 503 | Any | The database is under load. Back off and retry. |
Eight more codes are reserved in the vocabulary for request-parsing and platform failures
and are not answered today: malformed_json, malformed_body, unsupported_encoding,
unsupported_charset, request_aborted, not_implemented, gateway_timeout and
geo_restricted. A client that branches on code needs no special handling for them; an
unknown code falls through to the handling for its status.
Validation errors
A 422 adds invalidFields, with one entry per field that failed:
Code
msg is a translation key rather than an English sentence. Resolve it or show your own
copy.
4xx and 5xx
The distinction is meaningful. A 4xx reports a problem with what you sent and returns a
usable detail. A 5xx reports only that something failed on our side; it carries
errorId and is investigated by us.
If 5xx responses are arriving in numbers rather than one at a time, the
status page is the fastest way to tell a platform incident
from something specific to your integration, and to see when it is resolved without
retrying to find out. Quote the errorId if what you are seeing is not on it.
Request body size
Request bodies are capped at 10 MB. Exceeding the cap returns 413 with the limit
named in detail. This does not apply to file uploads, which go directly to storage
through signed URLs and never pass through the API.
