Error codes
The status codes, error types, and codes Atlas emits — and which are worth retrying.
Errors use the OpenAI status codes and body shape, so existing retry and backoff logic works unmodified.
{
"error": {
"message": "Your organization has reached its tokens-per-minute limit for this model.",
"type": "rate_limit_error",
"param": null,
"code": "rate_limit_exceeded",
"request_id": "req_01k4v9m2..."
}
}param and code are always present, explicitly null when unknown, because
that is what OpenAI sends and some clients read the keys unconditionally.
request_id is additive; it is also the x-request-id response header.
Always capture request_id. Prompt and completion content is not retained,
so the request id is the only handle Atlas has on a specific failed call. A
report without one is very hard to act on.
Error types
| Type | Status |
|---|---|
invalid_request_error | 400 |
authentication_error | 401 |
permission_error | 403 |
not_found_error | 404 |
rate_limit_error | 429 |
insufficient_quota | 429 |
server_error | 500 |
service_unavailable_error | 503 |
Both quota exhaustion and throttling are 429 so a client's existing backoff
handles them; the type and code distinguish them for a human reading a log.
Codes
Request problems — fix and resend
| Code | Status | |
|---|---|---|
invalid_request_body | 400 | The body is not a valid chat completion request. param names the field. |
unsupported_parameter | 400 | n greater than 1. One choice per request. |
model_does_not_support_tools | 400 | Tools sent to a model without tool calling. |
model_does_not_support_structured_output | 400 | json_schema sent to a model without it. |
unsupported_json_schema | 400 | The schema is outside the published subset. |
request_too_large | 413 | Body above the model's max_request_bytes. |
model_not_found | 404 | No such model identifier. |
model_retired | 404 | The identifier existed and has passed its shutdown date. |
backend_rejected_request | 400 | The serving backend refused the request as malformed. |
Every one of these is refused before admission: they cost no tokens and consume no rate-limit budget.
Authentication
A 401 authentication_error with code invalid_api_key is returned for every
credential problem, with the same body: a missing key, a malformed key, an
unknown key, a revoked key, an expired key, and a read-scoped key presented to
the inference endpoint all look identical.
{
"error": {
"message": "Incorrect API key provided. You can find your key in the Atlas console.",
"type": "authentication_error",
"param": null,
"code": "invalid_api_key",
"request_id": "req_..."
}
}This is deliberate. Distinguishing "no such key" from "revoked" would turn the
endpoint into an oracle telling an attacker which stolen keys are still worth
trying. If you are debugging a 401, check the key's revoked_at and prefix
in the console rather than reading anything into the response.
Output problems
| Code | |
|---|---|
structured_output_not_json | The model's content did not parse as JSON. |
structured_output_schema_violation | It parsed but did not validate against your schema. |
On a stream these arrive as an in-band error
frame after the
deltas, with no [DONE].
Capacity and infrastructure — retry
| Code | Status | Retry? | |
|---|---|---|---|
rate_limit_exceeded | 429 | Yes, honor retry-after | Your organization's limit for that model. |
server_overloaded | 503 | Yes, your own backoff | Atlas capacity, or Atlas's own upstream quota. No retry hint. |
server_shutting_down | 503 | Yes | A deploy is draining. |
backend_timeout | 503 | Yes | The serving backend did not respond in time. |
backend_unavailable | 500 | Yes | The backend could not be reached or is not configured. |
backend_protocol_error | 500 | Maybe | The backend's response could not be parsed. |
backend_error | 500 | Maybe | An unclassified backend failure. |
internal_error | 500 | Maybe | An Atlas fault. Report the request_id. |
Client-side
| Code | Status | |
|---|---|---|
client_disconnected | 499 | You closed the connection before the response finished. Not an Atlas failure — but note that generated tokens are still metered. |
429 versus 503
The distinction Atlas cares most about you getting right:
429 rate_limit_exceeded
Your organization exceeded its own configured limit. Carries
retry-after, retry-after-ms, and the x-ratelimit-* headers. If this is
frequent under normal load, ask for a higher limit.
503 server_overloaded
You were within your limits; Atlas capacity was the constraint. Carries no quota headers and no retry hint at all.
The absence of headers on the overload is enforced, not incidental. A retry hint present on one cause and absent on the other would let a client infer quota state from a capacity event — and a hint Atlas invented would have every caller retrying on the same beat, against a service that is already saturated.
Two causes map to server_overloaded: Atlas's fleet being saturated, and Atlas
exhausting its own quota with an upstream provider. They are deliberately
indistinguishable to you, because your correct action is identical.
If your alerting groups "5xx and 429" into one series, you cannot tell these apart — and only the 429 is something you can act on. Split them.
Retry guidance
- Use your SDK's built-in retry. The OpenAI clients already retry 429 and
5xx with exponential backoff and jitter, and they already prefer
retry-after-msoverretry-after. - Do not retry 400, 404, or 413. Nothing about a resend changes the answer.
- Do not retry a 401 until you have checked the key. Repeated retries with a revoked key will not start working.
- Treat a stream that ended without
[DONE]as a failure, whatever the status was — see Streaming.