API errors
Every rejected call returns the same JSON shape, whatever went wrong. Read the code, not the
message — the code is stable, the wording is not.
The envelope
{
"error": {
"code": "access_denied",
"message": "invalid or expired widget token",
"correlation_id": "9f3c…",
"details": [
{ "field": "question", "message": "must not be blank" }
]
}
}
correlation_id is present when you send one, as X-Correlation-Id or X-Request-Id. Send one
per request and log it — it is what lets us find your call in our logs. details appears only
for field-level validation failures.
Codes
| Status | code | What happened | Retry? |
|---|---|---|---|
| 401 | access_denied | No token, or one that is unknown, expired or presented from a domain it does not allow. | No — issue a new token |
| 403 | access_denied | The token is valid but not for this knowledge base. | No |
| 403 | entitlement_missing | The workspace is not entitled to this module. | No |
| 402 | billing_denied | The workspace has no spend authorisation left. | No — top up |
| 422 | schema_invalid | question is missing or blank. | No |
| 413 | payload_too_large | The request body exceeds the limit. | No |
| 429 | rate_limited | Too many requests. | Yes, after a pause |
| 503 | kb_reindexing | The knowledge base is rebuilding its index and cannot answer yet. | Yes |
| 503 | dependency_unavailable | A service behind the answer is down. | Yes |
| 503 | service_auth_unavailable | Internal authentication is temporarily unavailable. | Yes |
| 504 | timeout | The answer took too long. | Yes |
| 500 | internal_error | An unhandled fault on our side — including a request body that is not valid JSON, which is not reported as a validation error. | Once, then report it |
Back off between retries rather than repeating immediately — a retryable code means the condition is expected to clear, not that it has.
The three you will actually hit
401 within an hour of working. The token expired. Tokens last 60 minutes by default; see Developer access and widget tokens.
401 from a browser but not from curl. The page's origin is not on the token's list. A call
from your own server sends no Origin header and is accepted; a browser one must match exactly,
scheme and port included — https://app.example.com does not cover http://app.example.com.
503 kb_reindexing. Someone changed the knowledge base's embedding settings and it is
rebuilding. Nothing is wrong; queries resume when it finishes.
Not an error
A 200 with abstained: true means Elie found no evidence good enough to answer from. Show the
message to the reader — retrying the same question produces the same result.
Next
- Query endpoints — the calls these codes come from.
- Troubleshooting — symptoms across the whole product.