Errors
Every error — validation, billing, upstream — uses one envelope the OpenAI SDKs already parse. The same broken request always gets the same error, so retry logic can trust what it reads.
The envelope
every error, every endpoint
{
"error": {
"message": "max_tokens (32768) exceeds the limit for your organization (16384).",
"type": "invalid_request_error",
"param": "max_tokens",
"code": "max_tokens_exceeds_cap",
"metadata": {}
}
}type— stable taxonomy for branching (matches the table below).code— the precise machine-readable case.param— the offending field, when one exists.metadata— extra machine data on some errors, e.g. the available balance on a 402.
All error codes
| Code | HTTP | Type | Meaning |
|---|---|---|---|
context_length_exceeded | 400 | invalid_request_error | The input plus max_tokens exceeds the model context window. The message carries the estimated total and the limit. Shorten the conversation or lower max_tokens; rejected requests are not billed. |
image_url_not_supported | 400 | invalid_request_error | Images must be inlined as a base64 data: URI. Remote http(s) image links are not fetched. |
invalid_json | 400 | invalid_request_error | The body is not valid JSON. Look for a trailing comma or a truncated payload. |
invalid_request | 400 | invalid_request_error | The model rejected the body as invalid for this model. |
json_mode_no_stream | 400 | invalid_request_error | A JSON response format cannot be combined with streaming. Ask for JSON without stream. |
json_mode_not_supported | 400 | invalid_request_error | This model cannot enforce a JSON response format. |
max_tokens_exceeds_cap | 400 | invalid_request_error | The requested output length is above your organization cap. The message states the current cap. |
missing_messages | 400 | invalid_request_error | No messages array in the body. |
missing_model | 400 | invalid_request_error | No model field in the body. Send an id from GET /v1/models. |
n_not_allowed | 400 | invalid_request_error | Asking for more than one completion is disabled by default because it multiplies the bill silently. Contact support to enable it. |
reasoning_effort_not_supported | 400 | invalid_request_error | This model does not accept that reasoning_effort level; the message lists the ones it does. |
tools_not_supported | 400 | invalid_request_error | This model cannot do tool calling. Pick a model that lists tools in its capabilities. |
unknown_parameter | 400 | invalid_request_error | A top-level field is not part of the API; param names it. Almost always a typo. |
vision_not_available | 400 | invalid_request_error | This model cannot read images. Pick a model that lists vision in its capabilities. |
invalid_api_key | 401 | authentication_error | The key is unknown. Check you copied it whole, including the sk-gw- prefix. |
ip_not_allowed | 401 | authentication_error | The key is valid but the calling IP is outside its allowlist. Update the allowlist on the key. |
key_expired | 401 | authentication_error | The key is past its expiry date. Create a new one. |
key_revoked | 401 | authentication_error | The key was revoked in the dashboard. Create a new one. |
insufficient_credits | 402 | insufficient_credits | The balance does not cover the estimated cost of this request; metadata carries the available amount and the estimate. Top up, then retry. |
missing_scope | 403 | permission_error | The key lacks the scope this endpoint needs, for example usage:read. |
model_not_allowed | 403 | permission_error | The model exists but is outside the allowed models list of this key. |
org_suspended | 403 | permission_error | The organization is suspended. Retrying will not help — contact support. |
generation_not_found | 404 | not_found_error | No request with that id belongs to your organization, or it has not settled yet. Retry in a few seconds. |
model_not_found | 404 | not_found_error | No such model, or it is disabled. See GET /v1/models for the current list. |
route_not_found | 404 | not_found_error | The path does not exist. If you hit /chat/completions without /v1, your base_url is missing "/v1"; a /v1/v1/ path means it has one too many. |
request_timeout | 408 | timeout_error | The model did not answer in time, after our internal retries. Retry with backoff. |
idempotency_key_in_flight | 409 | invalid_request_error | The same Idempotency-Key is still being processed. Wait for the first attempt to finish. |
request_too_large | 413 | invalid_request_error | The body is over the size limit. Trim the context or split the work. |
idempotency_key_reused | 422 | invalid_request_error | The same Idempotency-Key was sent with a different body. Use a fresh key per distinct request. |
daily_budget_exceeded | 429 | rate_limit_error | The daily spend cap is reached. retry-after counts to midnight UTC, so raise the budget instead of waiting it out. |
rpm_exceeded | 429 | rate_limit_error | Too many requests this minute. retry-after gives the seconds left in the window; the same code with retry-after 1 means concurrency slots are momentarily full. |
tpm_exceeded | 429 | rate_limit_error | The per-minute token budget is spent. retry-after gives the seconds left in the window. |
internal_error | 500 | api_error | Something failed on our side. The incident is recorded automatically; retry, and contact support if it persists. |
upstream_config_error | 500 | api_error | A model mapping on our side is wrong. Our team is alerted automatically. |
upstream_error | 502 | api_error | The provider failed after our retries. Retry with backoff. |
no_capacity | 503 | service_unavailable_error | Capacity is temporarily unavailable. retry-after is 10 seconds and the request was not billed. |
Errors inside a stream
After streaming starts, HTTP is already 200 — failures arrive as a final chunk with finish_reason: "error" carrying this same envelope in an error field. Sample and handling code: Streaming.
Retry guidance
- Retry with backoff:
request_timeout,upstream_error,no_capacity, and 429s withretry-after— the OpenAI SDKs already do this. - Fix before retrying: every 400/401/403/404/413/422 — the request itself is the problem.
- Act, don't retry:
insufficient_credits(top up) anddaily_budget_exceeded(raise the budget or wait for 00:00 UTC).
Catching the 402 in your code
The OpenAI SDKs have no dedicated exception class for 402 — it surfaces as the generic APIStatusError (Python) / APIError (Node), so a handler written only for RateLimitError or AuthenticationError will miss it. Branch on the status or on error.code:
catching 402
# Python
import openai
try:
resp = client.chat.completions.create(...)
except openai.APIStatusError as e:
if e.status_code == 402:
print("Out of credits:", e.body["error"]["message"]) # includes balance + top-up link
else:
raise
// Node
try {
const resp = await client.chat.completions.create({...});
} catch (err) {
if (err instanceof OpenAI.APIError && err.status === 402) {
console.error('Out of credits:', err.error?.message);
} else throw err;
}