Errors
The error envelope, HTTP status codes, machine-readable error codes, errors inside streams, and how to report a failure.
On this page
The error envelope
Most errors come in one JSON envelope with the matching HTTP status. The Anthropic-compatible endpoints use Anthropic’s envelope, and a few responses from the edge of the platform carry no envelope at all: see Responses without the envelope.
{
"error": {
"status": 403,
"message": "Access denied",
"code": "AUTHZ_2001",
"detail": "API key missing required scope: llm:access or llm:chat",
"request_id": "6c5d0c9e-0000-4000-8000-000000000000"
}
}
| Field | Meaning |
|---|---|
status |
The HTTP status code. |
message |
A short, display-safe summary for the status. |
code |
A machine-readable code. Branch on status first, then code. |
detail |
What went wrong with this request. Do not parse it; wording can change. |
errors |
On 422 only: one entry per invalid field, with field, message, type and, for numeric bounds, constraints. |
request_id |
The request’s correlation id. |
Every response, success or failure, carries an X-Request-ID header. An unexpected server error also carries X-Error-ID. Include both when you contact support.
Status codes
| Status | Code | When |
|---|---|---|
400 |
VALIDATION_4002 |
The request is malformed or fails a server-side check. |
401 |
AUTH_1001, or another AUTH_10xx code |
No valid credential: missing, malformed, expired or revoked. |
402 |
see below | No spendable credit, or the spend cap is reached. |
403 |
AUTHZ_2001, or another AUTHZ_20xx code |
The credential is valid but lacks a scope or permission, or the account is suspended or inactive. |
404 |
RESOURCE_3001 |
The resource does not exist or belongs to someone else. |
409 |
RESOURCE_3003 |
A replay of an idempotent request, or another conflict. |
413 |
none | The request body is over 10 MiB. No envelope; see below. |
422 |
VALIDATION_4001 |
The body or parameters fail schema validation. |
428 |
see below | A step-up is required, for example a recent sign-in to create a key. |
429 |
RATE_5001 |
A per-key LLM API limit was exceeded, or the limiter is unavailable. The per-user limits answer without the envelope; see below. |
500 |
INTERNAL_9002 |
An unexpected error. The response also carries X-Error-ID, and the body never contains internal details. |
500 |
INTERNAL_9001 |
An internal error the server handled and reported deliberately. |
502 |
EXTERNAL_6004 |
An upstream engine returned an unusable response. |
503 |
INTERNAL_9003 |
A dependency is temporarily unavailable. |
504 |
EXTERNAL_6005 |
An upstream engine timed out. |
Stored responses, batches and files that belong to another user or organization return 404, not 403, so a response never confirms that someone else’s object exists.
Responses without the envelope
These responses are produced before a request reaches an endpoint, or on a path that keeps a structured body, so they do not use the envelope:
| Status | Body | When |
|---|---|---|
400 |
{"detail": "Malformed Content-Length header."} |
The Content-Length header is not a number. |
413 |
{"detail": "Request too large"} |
The body is over 10 MiB. The wording can differ slightly. |
429 |
{"detail": "...", "retry_after": 60} |
A per-user limit on the Platform API, embeddings or rerank. Also sent with Retry-After: 60. See Rate limits. |
503 |
{"error": {"message": "...", "type": "transient_timeout", "code": "...", "recovery_exhausted": true}} |
A non-streaming chat completion finished without an answer. code is empty_completion or reasoning_budget_exhausted. There is no status or request_id field; read X-Request-ID from the headers. |
A robust client branches on the HTTP status first, reads error.code when the body has one, and otherwise falls back to detail. Retry the 503 above with backoff.
Errors inside a stream
Once a streaming response has started, its HTTP status is already 200. A failure after that point arrives as one final event, followed by the end marker:
data: {"error": {"message": "The AI model is temporarily unavailable. Please try again shortly.", "type": "transient_timeout", "code": "stream_interrupted"}}
data: [DONE]
Treat any data: event with an error object as a failed request. Codes you may see include context_length_exceeded, provider_rate_limited, stream_interrupted, incomplete_stream, empty_completion and budget_exhausted.
Anthropic-compatible errors
The Messages API returns Anthropic’s envelope with the same HTTP status, so Anthropic SDKs raise their usual exceptions:
{ "type": "error", "error": { "type": "rate_limit_error", "message": "Rate limit exceeded: <limit> requests/minute" } }
Retry-After and X-Request-ID are passed through, and x-should-retry: false is set when retrying cannot help (402, 409).
Retrying
| Retry? | Statuses |
|---|---|
| Yes, with backoff | 429, 502, 503, 504, and stream errors with transient_timeout |
| After fixing the request | 400, 413, 422 |
| After fixing credentials or permissions | 401, 403, 428 |
| After adding credit or raising the cap | 402 |
| Not as-is | 409 (send a new Idempotency-Key if you mean to run it again) |
Honour Retry-After when present. Use exponential backoff with jitter, cap the number of attempts, and send an Idempotency-Key so a retry after a lost response cannot run twice.