Errors
An error keeps the shape of the format you used, and its message is written by zdr.sh. Every error carries x-request-id, retry-after when waiting helps, and x-should-retry.
The shapes#
An OpenAI-format request, on /v1/chat/completions, /v1/responses, /v1/tools/search, /v1/embeddings and /v1/models, gets:
type is the OpenAI type of the status: invalid_request_error, authentication_error, insufficient_quota, permission_error, not_found_error, request_too_large, rate_limit_error, timeout_error or server_error. code is the machine-readable class below, and param names the offending field when there is one.
An Anthropic-format request gets the Anthropic envelope with the request id:
The Anthropic type of the status: invalid_request_error, authentication_error, billing_error for a 402, permission_error, not_found_error, request_too_large, rate_limit_error, overloaded_error for a 503, timeout_error, and api_error for the rest of the 5xx.
An error after a stream has started is an event of the stream, not a status: an OpenAI error chunk on chat, an Anthropic error event, and on Responses a response.failed event with status, retry_after_ms and x_should_retry.
The classes#
| Code | Status | Meaning | What to do |
|---|---|---|---|
invalid_request | 400 | The body could not be parsed or fails validation | Fix the request; retrying it unchanged will not help |
content_filter | 400 | A content or safety filter refused the request. A refusal at the edge may arrive as a 403 and that status is passed through | Change what you ask |
injection_suspected | 400 | The key's injection guard is set to block and the request carried a high-severity finding | Remove the flagged content, or set the guard to warn |
stateless_only | 400 | A Responses request pointed at stored state | Send the whole conversation in input |
context_length | 400 | The input does not fit the model's window. OpenAI-format clients get context_length_exceeded, which agents match to compact and retry | Trim the conversation, or pick a larger window |
request_too_large | 413 | The body is over 32 MiB | Send less; move large files out of the request |
auth | 401 | No credential, or the key is wrong, revoked or expired | Check the key; the message says which |
insufficient_balance | 402 | The balance cannot cover the hold the request needs | Top up at zdr.sh |
forbidden | 403 | The key may not do this: a model outside its allowed list, an IP outside the allowlist, a suspended account | Check the key's settings |
insufficient_scope | 403 | The key's scopes do not include this endpoint | Use a key with the endpoint's scope |
web_tools_blocked | 403 | The key's settings block the web tools | Turn the key's web tools on, or send the image inline as a data: URL |
not_found_model | 404 | No enabled model serves that id | Check the id against GET /v1/models |
model_endpoint_mismatch | 400 | A chat model sent to embeddings, or an embedding model sent to chat, messages or responses | Send the model to the endpoint its kind belongs to |
model_has_no_vision | 400 | The model does not accept the images the request carries | Use a model with vision, or remove them |
model_has_no_tools | 400 | The model does not accept the tools the request carries | Use a model with supports_tools, or send no tools |
image_generation_unsupported | 400 | A Responses request asks for the image_generation hosted tool | Send the prompt without the tool |
endpoint_not_supported | 404 | The path is an audio, image or rerank endpoint of another provider's API, which this one does not serve | Do not retry; quickstart lists the endpoints |
rate_limited | 429 | A per-key rpm or tpm was hit, or the account is at its concurrency cap | Wait for retry-after; the x-ratelimit-* headers say which limit |
daily_cap_reached | 429 | The key's daily spend cap is used up. Resets at 00:00 UTC | Wait, or raise the cap |
monthly_cap_reached | 429 | The key's monthly spend cap is used up. Resets on the 1st at 00:00 UTC | Wait, or raise the cap |
memory_tool_call_ceiling | 400 | The model asked for more memory calls in one round than the service allows; the request was refused before any of them ran | Ask a narrower question, or retry |
idempotency_key_reused | 400 | The Idempotency-Key was already used with a different body | Use a new key for a different request |
idempotency_in_progress | 409 | A request with this Idempotency-Key is still running | Retry when the first request has finished |
idempotency_unreplayable | 409 | The first response cannot be replayed: over 4 MiB, or a stream that did not finish | Send a new request with a new key |
upstream_unavailable | 503 | No model could serve the request, or a dependency failed | Retry with backoff; it is on our side |
capacity_unavailable | 503 | Inference is paused while a shared resource drains. Nothing was charged | Wait for retry-after and retry |
timeout | 504 | The model did not answer in time | Retry; a streamed request is less exposed to it |
internal | 500 | Something failed on our side | Retry once, then contact support with the x-request-id |
Retrying#
- A 429 and a 503 carry
retry-afterin whole seconds. A 503 without one carries two seconds. - Every error carries
x-should-retry,truefor a 429 and a 5xx. - 503 and 504 are safe to retry with exponential backoff. A request is settled for what it actually produced, so a retry after a failed stream does not pay twice.
- A 400, 401, 402, 403 and 404 are not worth retrying unchanged.
- A retry is a new request unless it carries an
Idempotency-Key, which returns the first answer instead of charging again. Chat completions has the rules.
The request id is issued by the service and a client-sent value is ignored, so a request cannot settle twice under one id.