Errors
Every status code the API returns, the two body shapes, what to do and when to retry.
Every error has an HTTP status and a JSON body. The message ends with a request id, for example (request id: 202611020537...). Keep it: support can find your request with it.
Body shapes
/v1/chat/completions, /v1/responses, /v1/models, /v1/images/*, /v1/videos/*, /v1/audio/* and /v1/embeddings answer in OpenAI's shape. This is a real answer to a request with a wrong key:
{"error": {"code": "", "message": "Invalid token (request id: 202611020537046636626168268d9d6vfJBhWIJ)", "type": "api_error"}}| Field | Meaning |
|---|---|
error.message | What went wrong, with the request id |
error.type | For example api_error or invalid_request_error |
error.code | A short code such as insufficient_user_quota or model_not_found; often empty |
error.param | Present on some errors; usually empty |
Errors from the model on /v1/messages use Anthropic's shape:
{"type": "error", "error": {"type": "api_error", "message": "... (request id: ...)"}}Errors found before the request reaches the model (wrong key, rate limit, a model the key may not use) come back in the OpenAI shape shown in the first tab. Read both.
POST /v1/jobs and GET /v1/jobs/{id} answer with {code, msg, data}. code repeats the HTTP status:
{"code": 404, "msg": "Task not found", "data": null}Two cases come back in the OpenAI shape instead, because they are answered before the job is read: a missing or wrong key (401) and the rate limit (429).
A job that started and then failed is not an HTTP error. GET /v1/jobs/{id} answers 200 with data.status set to failed and the reason in data.error. A failed job costs nothing.
Status codes
| Status | Meaning | What to do | Retry? |
|---|---|---|---|
400 | The request is invalid: a missing field, a wrong value, a field the model does not take, or a refusal by the content rules | Fix the request using the model's fields on Models | No. A content refusal is final; do not reword around it |
401 | No key, a wrong key, or a key that is turned off or expired | Check the Authorization: Bearer sk-... header and the key in API keys | No |
403 | Balance too low (insufficient_user_quota), the key may not use this model, or your IP is not on the key's allowlist | Top up in Wallet, or change the key's model list or IP allowlist | No, not until you fix the cause |
404 | Wrong path, or no job or video with this id on your account | Check the URL and the id | No |
413 | The request body is too large (over 128 MB) | Send files as https:// links instead of inline data | No |
429 | Rate limit: 120 requests a minute, 60 of them successful. On jobs it can also mean the model is at capacity | Wait for the Retry-After header (seconds), then retry. See Rate limits | Yes, after the wait |
500 | An error on our side or the model's | Retry with backoff | Yes, up to 3 times |
502 | The model's server sent a bad or empty answer | Retry with backoff | Yes, up to 3 times |
503 | We are briefly overloaded, or no server can take this model right now (model_not_found) | Check the model id on Models; then retry with backoff | Yes, up to 3 times |
504 | The model's server or a proxy on the way timed out | Retry with backoff | Yes, up to 3 times |
The model's own servers can return other statuses; they are passed to you as they come.
Timeouts
- A text call with
"stream": trueis closed if no data arrives for 5 minutes. - Other calls wait until the model answers.
POST /v1/images/generationswaits for the image, which can take a minute or more, so give your HTTP client a long timeout (several minutes). - For anything long, prefer Jobs: the call returns a job
idat once; you poll or get a webhook.
If your client times out on a POST /v1/jobs, the job may still have started. Check Task logs in Usage logs before you send it again. There are no idempotency keys yet, so a second request is a second job, charged again.
Retry rules
- Retry only
429,500,502,503and504. - Wait
Retry-Afterseconds when the header is present. Otherwise back off: 2, 4, then 8 seconds, plus a little random jitter. Stop after 3 tries. - Never resend a job that is still running: poll
GET /v1/jobs/{id}. - Do not retry
400,401,403or413. They fail the same way until you change the request, the key or the balance. Repeated content refusals can disable the account.
Code for backoff: Rate limits.
Errors during a stream
Once a stream has started, the status is already 200. An error then arrives as a last event in the stream. See Streaming.
