Errors
Every error on the model API comes in one envelope: the HTTP status, a message that says what to do, and a code your program can branch on.
The envelope
{"error": {"code": 404, "message": "The requested model does not exist or is not granted to this key. GET /api/v1/models lists the model aliases available to this key.", "metadata": {"error_type": "not_found", "gateway_code": "model_not_found", "param": "model"}}}codeis the HTTP status.metadata.gateway_codeis llm.buzz's own code, the one the tables below list.metadata.error_typeis OpenRouter's error type, for code written against OpenRouter.metadata.paramnames the request field at fault, when there is one.
Two surfaces keep their protocol's own format. The Messages API (/api/v1/messages) answers Anthropic's error body, {"type": "error", "error": {"type": "...", "message": "..."}}, where a 402 is a billing_error and a 403 a permission_error. The Responses WebSocket sends OpenAI's error frames.
A failure after a stream has started arrives as one last chunk with an error object and finish_reason: "error", followed by [DONE].
Dropped fields are not errors
A field the API does not know is dropped, and so is a sampling setting a model refuses, such as temperature on a model that takes none: the request still answers. Chat Completions, Responses and Messages name each one in x-sgr-ignored-parameters in the response body, as temperature->dropped(unsupported_by_provider). Check that list when a setting seems to have no effect. A field llm.buzz deliberately refuses is not dropped: it is a 400 before any provider is called, unsupported_parameter for a refused field or invalid_parameter for a refused value (Anthropic's invalid_request_error on Messages), listed below.
Retries
Retry a 429, 500, 502, 503 or 504 with backoff, and wait Retry-After seconds first when the answer carries it. Do not resend any other 4xx unchanged: fix the request, the key or the balance first. The exceptions are 402 in_flight_budget_exhausted, which you retry after Retry-After, and 409 idempotency_replay_unavailable, below.
On Chat Completions and Responses, send an Idempotency-Key header. A retry with the same key and the same body never runs the request twice: it returns the first answer, or 409 idempotency_replay_unavailable when that answer is not stored where the retry arrived.
That 409 means the first request ran or is still running, so retry with the same key after a short backoff: the retry may reach the stored answer. The exception is a first call that ended in an error answer: its key answers 409 for good, so send a new key. A retry without the key, or with a new one, runs the request again and is billed again, so send one only when a duplicate is acceptable. What to send next tells the cases apart.
The request
| Status | Code | What to do |
|---|---|---|
| 400 | invalid_parameter | A field fails validation, such as a missing messages or input, an out-of-range temperature, or n other than 1; metadata.param names it, or body when the fault spans fields. Fix the field. |
| 400 | invalid_request | llm.buzz read the body and the request was refused: a provider's own 400, whose sentence follows provider rejected the request:, or another refusal the message explains. Fix the request. |
| 400 | unsupported_parameter | You sent a field llm.buzz refuses, such as seed or logit_bias on Chat Completions or background on Responses; metadata.param names it. Remove it. Chat Completions, Responses and Messages list the fields each API refuses; Messages answers Anthropic's invalid_request_error, with (param: NAME) at the end of the message. |
| 400 | unsupported_capability | The model or its providers cannot do what the request asks, or, on Chat Completions and Responses, service_tier is flex, priority or fast: no model on llm.buzz has a price for a flex or priority tier today, and metadata.param names service_tier. Remove that part, or choose another model. |
| 400 | previous_response_not_found | Responses: previous_response_id names a turn llm.buzz does not have: expired, sent with store: false, lost, or made by another model. Resend the whole conversation in input. |
| 400 | context_length_exceeded | The prompt fits no provider's context window. Shorten it. |
| 400 | refusal | The model refused the request; metadata.refusal_reason names the category. |
| 400 | content_filter | A content filter stopped the request. |
| 409 | idempotency_conflict | An Idempotency-Key was reused with a different body. Use a new key for each distinct request. |
| 409 | idempotency_replay_unavailable | A request with this Idempotency-Key was already accepted, and its answer is not stored where this retry arrived, so it was not run again. The first call may still be running, or its answer may exist: retry with the same key after a short backoff first. A new key runs the request again and bills it again. If the first call ended in an error answer, the key answers 409 for good: send a new key. |
| 413 | request_too_large | The request body is over the size limit, such as an audio file over 25 MiB. Send less. |
| 426 | upgrade_required | A GET to /responses that is not a WebSocket upgrade. Open a WebSocket, or POST the request. |
An empty answer is not an error. When a provider bills tokens but sends no text, tool call or refusal, llm.buzz tries the next provider, and that empty attempt costs nothing. When every provider answers empty, the request answers 200 with the header x-gateway-warning: empty_completion, and the last attempt is billed the usage its provider reported, or an estimate of its prompt when it reported none: When a provider reports no usage.
Two prompt caching refusals are 400 invalid_parameter: a Claude cache layout that every provider of the model refuses (more than 4 breakpoints, or a one-hour breakpoint after a five-minute one), with param: cache_control (Anthropic's invalid_request_error on Messages), and, on Chat Completions and Responses, prompt_cache_retention: "24h" on a request that demands zero retention, with param: prompt_cache_retention. Prompt caching explains both.
The key and the balance
| Status | Code | What to do |
|---|---|---|
| 401 | invalid_key | Check the key, and that it is not disabled or revoked. |
| 402 | insufficient_credits | Your balance is spent and the model is not free. Add credit, or turn on auto top-up. Free models still answer. |
| 402 | weight_exceeds_budget | The request could cost more than your balance. Add credit, or lower max_tokens. |
| 402 | in_flight_budget_exhausted | Requests already running hold what your balance leaves. Retry after Retry-After seconds, or add credit. |
| 402 | insufficient_quota | Your organization's monthly budget is spent. The answer carries Retry-After, and the message names when the month resets. Wait for the reset, or ask an owner or admin of your organization to raise the budget on the console's Keys page. |
| 403 | key_limit_exceeded | The key's spend in its period has reached its limit. Wait for metadata.resets_at, or raise the limit. |
| 403 | management_key_not_allowed | A management key called a model route. Use an inference key. |
Authentication explains keys and their limits.
The key routes
The key routes, /api/v1/key, /api/v1/credits and /api/v1/keys, answer these besides invalid_key, and every other route refuses a management key:
| Status | Code | What to do |
|---|---|---|
| 400 | invalid_json | The body is not JSON. Send a JSON object. |
| 400 | invalid_parameter | A field is invalid; metadata.param names it. |
| 400 | invalid_request | A PATCH sets none of name, disabled, limit and limit_reset. Send at least one. |
| 401 | missing_authentication | The request has no Authorization header. Send the key as a bearer token. |
| 403 | management_key_required | An inference key called /api/v1/credits or /api/v1/keys. Use a management key. |
| 403 | management_key_not_allowed | A management key called a route other than the key routes, such as a model route or a model listing. Use an inference key. |
| 404 | key_not_found | The hash names none of your inference keys. List them with GET /api/v1/keys. |
The model and its providers
| Status | Code | What to do |
|---|---|---|
| 404 | model_not_found | No model has this name, or your key may not call it. Check the name against GET /api/v1/models. |
| 404 | model_unavailable | No provider of the model can serve it right now. Retry later, or choose another model. |
| 404 | no_allowed_providers | Your provider.only, ignore or order leave no provider. The message names the providers that serve the model. |
| 404 | model_not_available_in_region | Your organization's requests are kept in the EU, and the model has no EU provider. Choose a model with an EU provider. On Messages the 404 is Anthropic's not_found_error. |
| 404 | model_not_available_under_data_policy | No provider of the model meets provider.zdr, provider.data_collection or your organization's data policy. Remove the demand, choose another model, or review the policy on the console's Prompt logs page. |
Models and routing explains provider preferences and data policy.
A path that is not a route llm.buzz serves answers 404 unknown_route: check it against the Endpoints.
Retry later
| Status | Code | What to do |
|---|---|---|
| 429 | unavailable_route | Providers are throttling, or the model is at capacity. Wait Retry-After seconds, then retry. |
| 429 | gateway_overloaded | Too many requests are waiting for replay. Retry with backoff. |
| 429 | free_allowance_exceeded | A free offer's allowance is used: your organization's tokens, or your key's requests per minute. Choose a paid model, or for the per-minute limit retry after Retry-After seconds. |
| 502 | provider_output_too_large | The provider's answer was over llm.buzz's response limit. Ask for less output, for example with a lower max_tokens. |
| 502 | all_routes_failed | Every provider tried failed. Retry with backoff. |
| 503 | unavailable_route | No provider could answer just now. Retry with backoff. |
| 503 | continuation_store_unavailable | Responses: llm.buzz cannot reach its store of turns. Retry after Retry-After seconds. |
| 503 | gateway_unavailable | llm.buzz could not take the request. Retry after Retry-After seconds. |
| 503 | gateway_draining | The server that took the request is restarting. Retry after Retry-After seconds. |
| 504 | deadline_exceeded | The request passed its 600-second limit. A stream cut at the limit ends with an error chunk instead, and is billed for the prompt and the output you received. Retry, or ask for a shorter answer. |