# 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

```json
{"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"}}}
```

- `code` is the HTTP status.
- `metadata.gateway_code` is llm.buzz's own code, the one the tables below list.
- `metadata.error_type` is OpenRouter's error type, for code written against OpenRouter.
- `metadata.param` names 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](#the-request).

## 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](retries-and-idempotency.md#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](chat-completions.md#request-fields), [Responses](responses.md#request-fields) and [Messages](messages.md#request-fields) 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](usage-and-limits.md#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](prompt-caching.md) 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](authentication.md) 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](models-and-routing.md) 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](api-reference.md#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. |
