Concepts
Errors
Error shapes per endpoint, the common codes, and what to retry
Chat Completions, Responses and Messages return endpoint-specific error bodies; System One has its own envelope, described at the end. Account-level throttling uses a separate flat response, described below.
Shape
The body depends on which endpoint you called. Chat Completions and Responses share the OpenAI envelope; Messages uses the Anthropic one.
Chat Completions (/v1/chat/completions):
{
"error": {
"message": "The model does not exist or you do not have access to it.",
"type": "invalid_request_error",
"param": null,
"code": "model_not_found"
}
}Responses (/v1/responses) uses the same {"error": {...}} shape with its own type and code values — see its table below. Messages (/v1/messages):
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "..."
},
"request_id": "..."
}type is the coarse classifier, code the specific one, and param names the offending request field when there is one. Match on type and code, never on message — messages are for humans and may be edited. On Responses, code carries the public code when one exists and otherwise repeats the type. On Messages, error.code is optional, and request_id repeats the request-id and x-request-id headers that every Messages response carries. code is open-ended — a code you don't recognize is the internal error name in lowercase; fall back to type for it.
The common ones
What you're most likely to see, per endpoint.
Chat Completions
| Status | type | code | What to do |
|---|---|---|---|
| 400 | invalid_request_error | invalid_request_error | Malformed request or missing field — param names it. |
| 400 | invalid_request_error | model_not_capable | The model lacks the capability asked for (vision, tools, JSON mode). |
| 400 | invalid_request_error | context_length_exceeded | Trim the prompt or pick a model with more headroom. |
| 400 | invalid_request_error | content_policy_violation | Rejected by the safety filter. Don't retry verbatim. |
| 401 | authentication_error | invalid_api_key | No key, malformed, or unknown. Fix the Authorization header — see Authentication. |
| 403 | authentication_error | auth_insufficient_scope | The key can't call this endpoint. Use a key with access or contact support. |
| 403 | invalid_request_error | inference_free_tier_unavailable | :free isn't available for this model or account. Drop the suffix. |
| 404 | invalid_request_error | model_not_found | Unknown model ID. Check GET /v1/models. |
| 429 | rate_limit_exceeded | rate_limit_exceeded | Throttled. Back off per Retry-After — see Rate limits. |
| 429 | billing_error | billing_insufficient_balance, insufficient_quota | No eligible balance. Not a throttle — see below. |
| 429 | billing_error | billing_hold_creation_failed | We couldn't reserve the hold. Transient — retry once. |
| 429 | invalid_request_error | inference_free_tier_rate_limited | The :free window is used up. Wait the Retry-After seconds or drop the suffix. |
| 500 | server_error | system_internal_error | Unexpected failure on our side. Retry once, then send the X-Request-ID response header to support. |
| 503 | server_error | server_error, provider_error, timeout | Transient. Retry with backoff. |
One status, two meanings: a throttle says rate_limit_exceeded; a billing refusal says billing_error. The refusal's code varies with which balance came up short — billing_insufficient_balance or insufficient_quota — so classify on the type. The exception is the :free throttle, which carries type: invalid_request_error with the code above; match that one on code. Backing off won't change your balance. The same billing refusal is a 403 on Responses and Messages.
Responses
| Status | type | Meaning |
|---|---|---|
| 400 | invalid_request_error | Malformed request (including previous_response_id), capability or context mismatch. |
| 401 | authentication_error | Missing or invalid API key. |
| 403 | permission_denied_error | Billing refusal, or the key can't call this endpoint. |
| 404 | not_found_error | Unknown model ID. |
| 429 | rate_limit_exceeded | Throttled. Back off per Retry-After. |
| 503 | server_error | Transient. Retry with backoff. |
Two statuses on this endpoint don't use the shape above. A missing or invalid key (401) and a key without the inference.chat scope (403) return a flat body: {"code": "AUTH_1008", "message": "...", "request_id": "..."} — AUTH_1007 for no key, AUTH_1008 for an invalid key, AUTH_1015 for a missing scope. Match on code. A body that isn't valid JSON gets the same flat shape with a 400.
Messages
| Status | type | Meaning |
|---|---|---|
| 400 | invalid_request_error | Malformed request, bad content block, capability or context mismatch. |
| 401 | authentication_error | Missing or invalid API key. |
| 403 | permission_error | Billing refusal, or the key can't call this endpoint. |
| 404 | not_found_error | Model not found. |
| 429 | rate_limit_error | Throttled. Back off per Retry-After. |
| 503 | api_error | Transient. Retry with backoff. |
Every Messages response carries request-id and x-request-id with the same value, and error bodies repeat it as request_id — any of the three is the id to quote to support.
Model access
model_not_allowed (403) means the same thing on all three endpoints: the key isn't allowed this model. Use a key with access to it, or pick a different model.
The account limiter's flat 429
For these three endpoints, account-level throttling returns a flat 429 body with no type:
{
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please try again later.",
"details": { "retry_after": 3 }
}Classify it on code: "RATE_LIMIT_EXCEEDED" and back off per the Retry-After header. The window it enforces, and the headers that expose its state: Rate limits.
Streaming requests
With stream: true, the model, your key's access to it, and the tools, tool_choice and response_format fields are checked before the response starts — those refusals are normal HTTP errors with the statuses above. Everything that happens after that arrives inside the stream, behind an HTTP 200 and without a Retry-After header:
| Refusal | Chat Completions error.type | Responses error.code | Messages error.type |
|---|---|---|---|
| Concurrency or daily cap | rate_limit_exceeded | rate_limit_exceeded | rate_limit_error |
| No eligible balance | billing_error | permission_denied_error | permission_error |
| Context window exceeded | invalid_request_error, code context_length_exceeded | invalid_request_error | invalid_request_error |
| No provider, provider failure, timeout | server_error | server_error | api_error |
Read the type before you resend: a cap or a provider failure is worth a delayed retry; a balance or context refusal is not. A natural-language model name or auto on Chat Completions skips the pre-check, so every refusal is in-stream for those requests. Frame shapes: Streaming.
When to retry
| You got | Retry? | How |
|---|---|---|
429 throttle (rate_limit_exceeded / rate_limit_error, or the flat RATE_LIMIT_EXCEEDED) | Yes | Wait the full Retry-After; without one, back off exponentially. Cap your attempts. |
429 :free window (inference_free_tier_rate_limited) | Yes | Wait Retry-After, or drop the :free suffix. |
429 / 403 billing refusal (billing_insufficient_balance, insufficient_quota) | No | Top up or change plan. The OpenAI SDKs retry this twice unless you set max_retries=0. |
429 billing_hold_creation_failed | Once | Transient on our side. |
503 server_error, provider_error, timeout, api_error | Yes | Back off; honor Retry-After when present. Stop after a few attempts. |
| 500 | Once | Then send the request id to support: the X-Request-ID header, or on Messages the body's request_id — same value. |
| 400, 401, 403 (scope or model access), 404 | No | Fix the request, key or model first. content_policy_violation: don't resend the same input. |
| In-stream error after output started | Not automatically | A resend is a new, separately billed request and may repeat tool calls. See Streaming. |
| In-stream error before output | By its type | Same rules as the matching HTTP row; no Retry-After header is available. |
The OpenAI SDKs retry every 429 and every 5xx twice by default, including a billing refusal. Set max_retries=0 on the client and retry yourself on type, or accept two wasted attempts before the exception reaches you.
System One errors
/v1/systemone has its own error envelope: lowercase codes such as invalid_request and auth_insufficient_scope, an X-System-One-Error-Code header, and a request_id in both the body and X-Request-ID. See System One.
Related
Response-level backoff headers: Rate limits. API-key access: Authentication. Picking a model with the capability your request needs: Models.