# Errors and request identifiers

Source: https://platform.lyrouter.com/ai_gateway/docs/errors/en
Last updated: 2026-10-22

When LYRouter rejects a call, it returns a 4xx or 5xx status code. On OpenAI-compatible endpoints the error body is `{"error": {"message", "type", "code"}}`; on `/v1/messages` it is converted to the Anthropic format; a few responses are an HTML page or a single line of plain text. Look up the status code and the exact `error.message` in the quick reference on this page to tell whether the problem lies in the request itself, in the account and configuration, or with the platform or the upstream provider, and what to do about it. The gateway does not return a request ID in response headers. To pin down a specific call, open the request's detail in **Log analysis** and copy the Request ID at the top.

## Error response format

### OpenAI-compatible format

Apart from `/v1/messages`, every endpoint (Chat Completions, Responses, Embeddings, images, video and others) returns this error body by default:

```json
{
  "error": {
    "message": "insufficient credit balance",
    "type": "insufficient_quota",
    "code": "insufficient_credit_balance"
  }
}
```

| Field | Description |
| --- | --- |
| `message` | The error message, mostly in English and occasionally in Chinese. The messages listed on this page match what the gateway returns word for word; `<...>` marks the parts that are replaced |
| `type` | The error category, such as `invalid_request_error`, `permission_error`, `access_denied`, `guardrail_blocked`, `insufficient_quota`, `rate_limit_error`, `billing_unavailable`, `service_unavailable` or `api_error` |
| `code` | A machine-readable identifier. Only some errors have one; when there is none, the whole field is omitted. The same `message` can come with several `code` values (for example `insufficient credit balance`), so branch your program on `code` first |
| Additional fields | Carried by only a few errors: `limit`, `used`, `reset_at` and others on quota 429s; `retry_after` and `blocked_until` on circuit-breaker 429s; `upstream_status`, `generated_by` and others on error bodies filled in for an empty upstream response |

### Anthropic format (`/v1/messages`)

When you call through `/v1/messages`, both the gateway's own errors and upstream errors are converted to the Anthropic format, with `message` unchanged:

```json
{
  "type": "error",
  "error": { "type": "authentication_error", "message": "invalid api key" }
}
```

| HTTP status | `error.type` |
| --- | --- |
| 400, 402, 422 | `invalid_request_error` |
| 401 | `authentication_error` |
| 403 | `permission_error` |
| 404 | `not_found_error` |
| 413 | `request_too_large` |
| 429 | `rate_limit_error` |
| 503, 529 | `overloaded_error` |
| 500, 502, 504 and other 5xx | `api_error` |

After conversion, `error.code` and the additional fields (such as `limit` and `retry_after`) are dropped; response headers are kept as usual. The error messages on this page apply to `/v1/messages` too. When you need to tell specific causes apart, go by the status code plus `message`, or look up the request in the logs.

The 404 → `not_found_error` mapping above does not apply to a 404 for a path or HTTP method that does not exist: calling `/v1/messages` with GET, for example, returns 404 `{"error_msg":"404 Route Not Found"}`, which is not converted to the Anthropic format.

### Responses that are not JSON error bodies

| Situation | Status | Response body |
| --- | --- | --- |
| Connecting to the upstream failed, or waiting for the upstream timed out | 500, 504 | HTML error page |
| Wrong `Content-Type`, malformed multipart request, or a required native passthrough cannot be satisfied (converted to the Anthropic format on `/v1/messages`; see above) | 400 | One line of plain text, such as `unsupported content-type: text/plain, only application/json is supported` |
| The workspace has a default model set, and the request body is not valid JSON or is empty | 400 | `{"message": "..."}`, with no outer `error` |
| The path or HTTP method does not exist, such as `/v1/v1/chat/completions`, or calling `/v1/chat/completions` with GET | 404 | `{"error_msg": "404 Route Not Found"}` |

For a plain-text 400 returned by the gateway, the `Content-Type` response header is currently still `application/json`, so parsing it directly as JSON fails, and some SDKs report an error such as "unable to parse the response". A safe approach is to check the status code first, then check whether the body is JSON (try to parse it); if it parses, read `error.message`, `message` and `error_msg` in that order; if none is present, log the first few hundred characters of the raw response:

**Reading an error response**

cURL:
```bash
# -i also prints the status line and response headers (such as Retry-After and x-aigw-quota-*)
curl -sS -i https://api.lyrouter.com/v1/chat/completions \
  -H "Authorization: Bearer $LYROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"MODEL_ID","messages":[{"role":"user","content":"Hello"}]}'
```

Python:
```python
import os
import requests  # install it first with pip install requests; the OpenAI SDK does not include it

resp = requests.post(
    "https://api.lyrouter.com/v1/chat/completions",
    headers={"Authorization": f"Bearer {os.environ['LYROUTER_API_KEY']}"},
    json={"model": "MODEL_ID", "messages": [{"role": "user", "content": "Hello"}]},
    timeout=400,  # longer than the gateway's 6-minute wait for the upstream
)
if resp.status_code >= 400:
    try:
        body = resp.json()
    except ValueError:  # HTML error page or plain text
        body = {}
    err = body.get("error", body) if isinstance(body, dict) else {}
    message = err.get("message") or err.get("error_msg") or resp.text[:300]
    print(resp.status_code, err.get("code"), message)
```

Node.js:
```js
const resp = await fetch("https://api.lyrouter.com/v1/chat/completions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LYROUTER_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ model: "MODEL_ID", messages: [{ role: "user", content: "Hello" }] }),
});
if (!resp.ok) {
  const text = await resp.text();
  let err = {};
  try {
    const body = JSON.parse(text);
    err = body.error ?? body;
  } catch {
    // HTML error page or plain text
  }
  console.log(resp.status, err.code, err.message ?? err.error_msg ?? text.slice(0, 300));
}
```

## Quick reference

In the Who handles it column, "administrator" means the current Account Owner or a workspace Admin, and "Contact support" means the platform needs to act.

| Status | Error message (`error.message`) | `error.code` | Cause | Who handles it |
| --- | --- | --- | --- | --- |
| 400 | `you must provide a model parameter` | `missing_model` | No `model` in the request, and the workspace has no default model | Caller |
| 400 | `invalid type for 'model': expected a string` | `invalid_model` | `model` is not a string | Caller |
| 400 | `request body must be a JSON object` | `invalid_json` | The request body is not a valid JSON object; some versions return `missing_model` | Caller |
| 400 | `could not get parse JSON request body: <parse error>` | — (no outer `error`) | Same as above, when the workspace has a default model set | Caller |
| 400 | `unsupported content-type: <type>, only application/json is supported` | — (plain text) | `Content-Type` is not JSON | Caller |
| 400 | ``multipart body has no non-empty `model` field`` and others | — (plain text) | The multipart body is missing a field or has an invalid value | Caller |
| 400 | `This model does not support the <api> API` and others | `unsupported_model_api` and others | The model does not support this endpoint, operation or input type | Caller switches models |
| 400 | `unsupported parameters for this model: <parameter>` | `unsupported_parameters` | In strict mode, the request has parameters the model does not support | Caller |
| 400 | The specific parameter's validation message | `invalid_parameter` | Parameters or multipart content of image, video and similar endpoints are invalid | Caller |
| 400 | ``native passthrough was required by `x-gateway-passthrough: require` ...`` | — (Anthropic format on `/v1/messages`, plain text on other endpoints) | Native passthrough was required, but this request cannot be passed through | Caller |
| 401 | `missing credentials` | — | No API key | Caller |
| 401 | `invalid authorization scheme, expected 'Bearer'` | — | The `Authorization` header is malformed | Caller |
| 401 | `invalid api key` | — | The key does not exist, or was deleted, disabled or has expired | Caller / administrator |
| 401 | `api key verification unavailable` | — | The platform's key verification service is temporarily unavailable | Retry later |
| 402 | `insufficient credit balance` | `insufficient_credit_balance` and others | The account balance is insufficient | Account Owner tops up |
| 402 | `subscription expired; renew to continue` | — | The subscription has expired | Account Owner renews |
| 403 | `api key expired` | — | The key is past its expiry time | Create a new key |
| 403 | `your IP address is not allowed` | — | The source IP is not on the key's allowlist | Caller / key creator |
| 403 | `ip access control misconfigured` | — | The key's IP allowlist is invalid | Key creator |
| 403 | `api key workspace is unavailable` | — | The workspace configuration temporarily cannot be read | Retry later |
| 403 | `Model is not available in the model plaza` | `model_not_available` | The workspace has not enabled this model | Administrator / switch models |
| 403 | `Request blocked by guardrail policy` | `guardrail_*` | Guardrails blocked the request | Caller / administrator |
| 403 | `Response blocked by guardrail policy` | `guardrail_sensitive` | Guardrails blocked the model's answer | Caller / administrator |
| 403 | `Subject is blocked by an active security ban` | `guardrail_subject_banned` | Guardrails have banned the key | Administrator |
| 403 | `该 API Key 已被永久封禁，无法恢复，请删除后创建新的 API Key。` (This API key has been permanently banned and cannot be restored; delete it and create a new API key.) | `api_key_permanently_banned` | The key tripped the error-rate circuit breaker for the 5th time | Fix the program, then create a new key |
| 404 | `404 Route Not Found` (in the `error_msg` field) | — | Wrong path or HTTP method | Caller |
| 404 / 409 | `video task not found`, `video is not ready yet` and others | `not_found` and others | The video task does not exist, has not finished or cannot be canceled | Caller |
| 429 | `rate limit exceeded on <subject> <metric>/<window>` | — | A quota or budget is used up | Wait for the reset / administrator raises the limit |
| 429 | `API Key 因最近 60 秒错误率过高已临时熔断，请在 N 秒后重试。` (The API key has been temporarily circuit-broken because its error rate over the last 60 seconds was too high; retry in N seconds.) | `api_key_circuit_open` | The key was temporarily circuit-broken for a high error rate | Fix the cause, then wait as long as `Retry-After` says |
| 429 | `all upstream credentials for this model are rate limited` | `upstream_quota_exhausted` | Every upstream credential for this model has reached its limit | Retry later / switch models |
| 429 | `upstream concurrency full` | `upstream_concurrency_full` | Upstream concurrency for async tasks is full | Retry later |
| 500 | HTML error page | — | Connecting to the upstream failed, or reading the response failed | Retry later |
| 500 | `sync spec needs the upstream model name for the path` and others | `model_unresolved` and others | A platform-side channel configuration problem | Contact support |
| 503 | `all routed candidates exhausted` | `no_route_candidate` | No model or channel can serve the request | Caller / administrator |
| 503 | `No configured credential can serve this model` | `no_route_candidate` | The model has no usable credential | Administrator |
| 503 | `Workspace model projection is unavailable` | `workspace_model_projection_unavailable` | The workspace's model configuration temporarily cannot be read | Retry later |
| 503 | `billing balance is temporarily unavailable` | `billing_maintenance` and others | The account balance temporarily cannot be confirmed | Platform (some cases recover with a later retry; contact support if it persists) |
| 503 | `Platform supplier cost snapshot is unavailable` | `supplier_cost_snapshot_not_ready` | The channel price for this model is not ready | Switch models and contact support |
| 504 | HTML error page | — | Waiting for the upstream's response timed out | Retry later |
| The upstream's status code | The upstream's error message | Defined by the upstream, or `upstream_*` | The upstream provider rejected the request | Depends on the message |

## 400: request format or parameters

The gateway returns every 400 in this section before sending the request to the upstream.

### Model field and request body

| Error message | `error.code` | Meaning and fix |
| --- | --- | --- |
| `you must provide a model parameter` | `missing_model` | `model` is missing, `""` or `null` (the same applies to an empty request body), and the workspace has no default model set in **Smart routing**. Put a model ID in the request; you can list them with [`GET /v1/models`](https://platform.lyrouter.com/ai_gateway/docs/models/en) |
| `invalid type for 'model': expected a string` | `invalid_model` | `model` was written as a number, an object or the like. A default model does not take its place even when one is set; change it to a model ID string |
| `request body must be a JSON object` | `invalid_json` | The request body is valid JSON, such as a string or a number, but not an object; when the request body is not valid JSON, the message is `could not get parse JSON request body: <parse error>`, also with `error.code` `invalid_json`. Some versions return `you must provide a model parameter` (`missing_model`) for both kinds of body; a JSON array as the request body also returns `missing_model`. Check quotes, commas and escaping, and whether the body actually went into `-d` / `json=` |
| `could not get parse JSON request body: <parse error>` (sic) | — | When the workspace has a default model set, invalid JSON returns this message with the response body `{"message": "..."}`; an empty request body returns `could not get body: request body is empty`. Fix as above |

When a default model is set, a chat request without `model` is answered by the default model instead, and you do not get `missing_model`. Setting the default model is covered in [Channels and routing](https://platform.lyrouter.com/ai_gateway/docs/routing/en).

### Content-Type and multipart (plain-text responses)

| Error message | Meaning and fix |
| --- | --- |
| `unsupported content-type: <type>, only application/json is supported` | The request's `Content-Type` header is not `application/json`. For JSON endpoints, set `Content-Type: application/json`; requests without this header are treated as JSON. `/v1/images/edits` and `/v1/videos` accept both JSON and `multipart/form-data` |
| ``multipart body has no non-empty `model` field`` | The multipart body has no non-empty `model` form field. When the workspace has no default model, `you must provide a model parameter` is returned first |
| `request body is empty` | The multipart request body is empty |
| ``<field> must be `true` or `false` in multipart form data`` | In `/v1/videos` multipart requests, `watermark` and `generate_audio` accept only the literals `true` or `false`; `1`, `yes` and `True` are all rejected |
| `input_reference must be a URL; uploading a file is not supported by this gateway (pass a publicly reachable https:// URL instead)` | In `/v1/videos` multipart requests, pass `input_reference` (the reference video) as a publicly reachable `https://` URL; uploading a file is not supported |

### Model does not support the endpoint or input

`type` is `invalid_request_error`; tell them apart by `error.code`:

| `error.code` | Error message | Meaning |
| --- | --- | --- |
| `unsupported_model_api` | `This model does not support the <api> API` | The model does not support this kind of endpoint; `<api>` is one of `chat_completions`, `responses`, `embeddings`, `rerank`, `images` and `videos`. For example, calling the image generation endpoint with a chat model |
| `unsupported_model_operation` | `This model does not support the <operation> operation` | The model supports this kind of endpoint but not this operation within it, such as `images_edits` or `videos_regenerations` |
| `unsupported_model_input` | `This model accepts text input only` | Images, audio or files were sent to a model that accepts only text |

Fix: check the endpoints and input types a model supports in **Model marketplace**, and switch to a model that supports them; see [Capabilities and compatibility](https://platform.lyrouter.com/ai_gateway/docs/capabilities/en).

### Unsupported or invalid parameters

**`unsupported parameters for this model: <parameter>` (`unsupported_parameters`)**: the request carries `X-Gateway-Strict-Parameters: true` and contains parameters the selected model does not support. Without this request header, the gateway drops those parameters, makes the call as usual and lists their names in the `X-Gateway-Unsupported-Parameters` response header. Both headers currently cover only image and video endpoints; parameters that chat endpoints drop during cross-protocol conversion (such as `response_format`, `seed` and `n`) neither trigger this error nor appear in this response header; see [Gateway headers and extensions](https://platform.lyrouter.com/ai_gateway/docs/gateway-headers/en). Fix: remove the parameters listed in the error, or switch to a model that supports them.

**`invalid_parameter`**: `error.message` states directly which parameter is wrong and how. Common ones:

| Error message | Meaning |
| --- | --- |
| `seconds must be a positive whole number of seconds` | The video duration must be a positive whole number of seconds |
| `watermark must be a boolean`, `generate_audio must be a boolean` | In JSON requests, these two fields must be booleans |
| `size implies an aspect ratio this model does not support: <value>` | The model does not support the aspect ratio implied by `size` |
| `cannot read the multipart body`, or a specific cause such as `truncated multipart body: no closing delimiter` or `inline file parts exceed the limit of 31457280 bytes` | A model that needs format conversion cannot read the multipart request body: the body was truncated, the files total more than 30 MB, and so on |
| `cannot rewrite the multipart model field: <reason>` | The gateway could not replace the `model` field in the multipart body with the upstream model name, and the request was not sent. Make sure `model` is a plain text field; if it persists, contact support |

Value ranges for each parameter are in [Multimodal capabilities](https://platform.lyrouter.com/ai_gateway/docs/multimodal/en).

### Native passthrough not available

Message: ``native passthrough was required by `x-gateway-passthrough: require` but is not available for this request (<reason>)``

A request with `x-gateway-passthrough: require` means "the request body must be sent as is to an Anthropic-protocol upstream, with no conversion". When the gateway cannot do that, it rejects the request outright instead of quietly converting it; falling back to a channel that does not use the Anthropic protocol also ends with this error. On `/v1/messages` this error is returned in the Anthropic format, with `message` identical to the message above: `{"type":"error","error":{"type":"invalid_request_error","message":"native passthrough …"}}`; on other endpoints it is a single line of plain text. The parentheses give the reason for the refusal:

| Reason | Meaning |
| --- | --- |
| `client_protocol` | The call was not made through `/v1/messages` |
| `route_disabled` | Request-side native passthrough is not enabled on the endpoint |
| `wire_protocol` | The selected channel is not an Anthropic-protocol upstream |
| `endpoint_type` | Not a chat endpoint |
| `channel_disabled` | Native passthrough is turned off for this channel |
| `preset_injection` | The virtual model used rewrites the request body (appends a system prompt, sets or locks parameters, cache strategy) |
| `shape_oai` | The request body is not in the Anthropic format |
| `decision_error` | The gateway's decision process failed |

`/v1/messages` currently does not have request-side native passthrough enabled, so requests carrying this header are rejected with `route_disabled`. Remove the header (or change it to `prefer`) and the request goes through the normal protocol conversion path; see [Anthropic Messages](https://platform.lyrouter.com/ai_gateway/docs/anthropic-messages/en).

## 401: authentication failed

A 401 request cannot be attributed to any workspace and does not appear in the logs. Detailed troubleshooting for each cause is in [401 / 403: authentication and access](https://platform.lyrouter.com/ai_gateway/docs/auth-errors/en).

| Error message | Meaning and fix |
| --- | --- |
| `missing credentials` | The request carries no API key. Add `Authorization: Bearer <API Key>`; Anthropic-style clients can also use `x-api-key: <API Key>`. For tools configured through environment variables, open a new terminal after changing them |
| `invalid authorization scheme, expected 'Bearer'` | The `Authorization` header is not in the `Bearer <API Key>` format. `Bearer` is case-sensitive, so `bearer sk-...`, the bare key and `Basic ...` are all rejected; `Bearer` with no key after it also returns this message. When a request carries both `Authorization` and `x-api-key`, only `Authorization` is read |
| `invalid api key` | The gateway cannot find this key: it was copied incompletely or with spaces or line breaks; the key was deleted, disabled or has expired; its workspace or creator has been deactivated; or the key was just created and has not reached the gateway yet (usually a few seconds). Permanently banned keys are the exception: they keep returning 403 `api_key_permanently_banned` even after deletion. Copy the full key again; if the plaintext is lost, delete the key and create a new one; see [Workspaces and API keys](https://platform.lyrouter.com/ai_gateway/docs/api-keys/en) |
| `api key verification unavailable` | The platform's key verification service is temporarily unavailable; this has nothing to do with whether the key is correct. Retry later; if it persists, check [Service status](https://platform.lyrouter.com/ai_gateway/docs/status/en) |

## 402: insufficient balance or expired subscription

| Error message | `error.code` | Meaning and fix |
| --- | --- | --- |
| `insufficient credit balance` | `insufficient_credit_balance` | The account's Credits balance is used up; or, when you use BYOK and a platform service fee applies, the balance is not enough to pay the service fee. The Account Owner tops up in **Subscription & Credits** |
| `insufficient credit balance` | `balance_missing` | The account has no balance record yet, usually because it has never been topped up. Topping up fixes it; if it still appears after a top-up, contact support |
| `insufficient credit balance` | `billing_entity_missing` | The gateway cannot find the billing account for this key. This is a platform-side data problem; contact support |
| `subscription expired; renew to continue` | — | The account's subscription has expired and its grace period is over, so calls from every workspace under the account are rejected. Renewing restores them; accounts without a subscription never get this error |

`type` is `insufficient_quota`. The balance check happens before the call: as long as the balance is above 0, a call can start, and one call can push the balance below zero; only the calls after that are rejected. When the balance is insufficient, the gateway does not switch to another channel or billing method. Billing rules are in [Billing and Credits](https://platform.lyrouter.com/ai_gateway/docs/billing/en).

## 403: access denied

### API key and source

| Error message | `error.code` | Meaning and fix |
| --- | --- | --- |
| `api key expired` | — | The key is past its expiry time; the gateway checks this on every request. Create a new key to replace it; some time after expiry, the same key returns 401 `invalid api key` instead |
| `your IP address is not allowed` | — | The key has an IP allowlist, and the request's source IP is not on it (`type` is `permission_error`). The gateway checks the egress IP the request arrives from; when you call from a corporate network, through a proxy or from a cloud server, that IP may differ from the one your machine shows |
| `ip access control misconfigured` | — | The key's IP allowlist is empty or contains an invalid IP / CIDR, so the gateway rejects every request. The key's creator or the Account Owner fixes the allowlist |
| `api key workspace is unavailable` | — | The key is valid, but the gateway temporarily cannot read its workspace's configuration. This is a temporary platform-side problem; retry later |
| `该 API Key 已被永久封禁，无法恢复，请删除后创建新的 API Key。` (This API key has been permanently banned and cannot be restored; delete it and create a new API key.) | `api_key_permanently_banned` | The key tripped the error-rate circuit breaker for the 5th time and is permanently banned; the ban cannot be lifted, and the key keeps returning the same 403 even after you delete it. Right after the ban, the message is `该 API Key 因多次高错误率请求已被永久封禁，无法恢复，请删除后创建新的 API Key。` (This API key has been permanently banned after repeated high-error-rate requests and cannot be restored; delete it and create a new API key.); once the ban has synced to the gateway, it becomes the message in this row. Fix the program that keeps failing before you create a new key, or the new key will be circuit-broken too |

Permanent-ban responses carry the response header `X-RateLimit-Reason: api_key_permanently_banned`, and the rejected requests are not written to the logs.

### Model not enabled

**`Model is not available in the model plaza`** (`model_not_available`, `type` is `access_denied`): in the **Model marketplace** of the key's workspace, every provider of this model is not enabled.

- An administrator enables the model in **Model marketplace**, or the caller switches to an enabled model.
- Using `@vmodel/<name>` or `lyrouter/auto` on image or video endpoints also returns this error: those endpoints accept only a concrete model ID.
- When the workspace has a default model set, a chat request for a model that is not enabled is usually answered by the default model instead; you can confirm this in Model (actual) in the logs. See [Unavailable models](https://platform.lyrouter.com/ai_gateway/docs/unavailable/en).

### Guardrails

**`Request blocked by guardrail policy`** (`type` is `guardrail_blocked`): the request matched a blocking policy in the workspace's guardrails and was not sent to the model. The message does not include the policy name or the matched content; use `error.code` to tell the policy type:

| `error.code` | Policy matched |
| --- | --- |
| `guardrail_client` | Client access: restricts calls by source IP, tool name or User-Agent |
| `guardrail_context` | Intent security: the request's intent was judged risky |
| `guardrail_sensitive` | Sensitive data: the request contains personal information, credentials or sensitive keywords |
| `guardrail_injection` | Prompt injection: the request looks like a prompt injection |
| `guardrail_model` | Model access: the chosen model is not in the allowed range; for virtual models, the model actually selected is checked |

- **`Response blocked by guardrail policy`** (`guardrail_sensitive`): the answer to a non-streaming request matched a sensitive-data policy. The upstream had already generated the answer, so the call is billed as usual. A streaming request that has already started output is not interrupted; the matched content is masked in the output.
- **`Subject is blocked by an active security ban`** (`guardrail_subject_banned`): an intent security policy has its action set to ban, so the key that triggered it is banned, and every request is refused during the ban. The ban lifts at the automatic unban time set in the policy, or earlier when an administrator lifts it in **Security events**.

Retrying the same request gives the same result. The caller can change the request content or switch to an allowed model; administrators check match details in **Security events** and adjust policies in **Guardrails**; see [Guardrails and semantic auditing](https://platform.lyrouter.com/ai_gateway/docs/guardrails/en).

## 404 / 409: path or resource not found

| Status | Error message | `error.code` | Meaning and fix |
| --- | --- | --- | --- |
| 404 | `404 Route Not Found` (response body `{"error_msg": ...}`) | — | The gateway has no such path, or the path does not accept this HTTP method. A common cause is a Base URL with an extra or missing `/v1` (the OpenAI SDK's `base_url` should be `https://api.lyrouter.com/v1`, and the Anthropic SDK's is the root address); see [Base URLs and authentication](https://platform.lyrouter.com/ai_gateway/docs/call-gateway/en). These requests do not appear in the logs |
| 404 | `video task not found` | `not_found` | The video task ID does not exist, has expired or belongs to another workspace |
| 409 | `video is not ready yet` | `not_ready` | The task has not completed successfully yet, so the video cannot be downloaded. Keep polling the task status |
| 409 | `cancelling an in-flight video task is not supported: ...` | `cancel_not_supported` | A task in progress cannot be canceled (the upstream offers no cancel endpoint). You can still delete it after it ends |

`previous_response_id` in `POST /v1/responses` is validated by the upstream provider, and an invalid one returns the upstream's error; see [Responses](https://platform.lyrouter.com/ai_gateway/docs/responses-api/en). When a video task itself fails, the query endpoint returns HTTP 200, with the task object's `status` set to `failed` and `error.code` set to `deadline_exceeded` (timed out; the query endpoint may not show it at present, see [Asynchronous tasks](https://platform.lyrouter.com/ai_gateway/docs/async/en)), `canceled` (canceled) or the error code given by the upstream (`upstream_error` by default), sometimes with the upstream's request ID in `error.provider_request_id`; see [Asynchronous tasks](https://platform.lyrouter.com/ai_gateway/docs/async/en).

## 429: rate limits, circuit breaking and upstream limits

| Error message | `error.code` | Meaning and fix |
| --- | --- | --- |
| `rate limit exceeded on <subject> <metric>/<window>` | — | The key's budget or a guardrail quota policy has reached its limit, such as `rate limit exceeded on apikey total_tokens/day`. The error body carries `limit`, `used` and `reset_at` (quota policies also carry `policy_code` and `policy_name`). Call again after `reset_at`, or ask an administrator to raise the limit |
| `API Key 因最近 60 秒错误率过高已临时熔断，请在 N 秒后重试。` (The API key has been temporarily circuit-broken because its error rate over the last 60 seconds was too high; retry in N seconds.) | `api_key_circuit_open` | This key had at least 100 requests in the last 60 seconds and more than 90% of them failed, so it was temporarily circuit-broken. The `Retry-After` response header gives the number of seconds to wait, and `retry_after` and `blocked_until` (UTC) in the error body match it. Fix the cause of the failures first, then wait |
| `all upstream credentials for this model are rate limited` | `upstream_quota_exhausted` | Every upstream channel or credential that can serve this model has reached its own limit; the error body carries `metric` and `window`. Retry later or switch to another model; if the model goes only through BYOK, ask an administrator to check the credentials' limits |
| `upstream concurrency full` | `upstream_concurrency_full` | When an async task was submitted (such as `POST /v1/videos`), the upstream account's concurrent tasks were full, and the request was not sent. Retry later |

- **Quota fields**: `<subject>` is `apikey`, `member`, `dept` or `ws`; `<metric>` is `requests`, `total_tokens`, `prompt_tokens`, `completion_tokens` or `credits`; `<window>` is `minute`, `minutes`, `day`, `month` or `total` (cumulative, never resets). Windows are aligned to Beijing time, and usage is counted only after a request completes.
- **Response headers**: when quotas are configured, responses to calls that go through gateway routing and the quota check (chat, embeddings and the like; both successful responses and quota 429s) carry `x-aigw-quota-<metric>-limit` / `-remaining` / `-reset`, where the metric is `credits`, `tokens`, `requests`, `input-tokens` or `output-tokens`; `/v1/messages` also carries `anthropic-ratelimit-*`, but credits have no matching header, so when only Credits quotas are configured the gateway does not generate `anthropic-ratelimit-*`.
- **Retry-After**: set only by the circuit breaker (`api_key_circuit_open`), together with `X-RateLimit-Reason: api_key_circuit_open` and `X-RateLimit-Reset` (Unix seconds); quota 429s do not carry it. The upstream's own `Retry-After` is forwarded only on `/v1/messages`.
- **Circuit-breaker steps**: 30 seconds the 1st time, 2 minutes the 2nd, 10 minutes the 3rd, 12 hours the 4th, and a permanent ban the 5th; trips accumulate over the key's whole lifetime. 402, 403 and 429 responses returned by the gateway also count as failures; circuit-broken requests are not written to the logs.

Full details are in [429: rate limits and quotas](https://platform.lyrouter.com/ai_gateway/docs/rate-errors/en) and [Budgets, quotas and rate limits](https://platform.lyrouter.com/ai_gateway/docs/quota/en).

## 500 / 504: upstream connection failed or timed out

| Status | Response body | Meaning |
| --- | --- | --- |
| 504 | HTML error page | Waiting for the upstream's response timed out, or connecting to the upstream timed out |
| 500 | HTML error page | Connecting to the upstream failed (for example, the connection was refused or the TLS handshake failed), or reading the upstream's response failed |

- The gateway's read timeout for the upstream is 6 minutes. For streaming requests it is measured between two consecutive pieces of output, so the request does not time out as long as output keeps coming.
- Before returning, the gateway has already tried other channels under the rules in [Retries and fallback](https://platform.lyrouter.com/ai_gateway/docs/fallback/en), and returns only after every candidate has failed. Once a streaming request has started output, the gateway no longer switches channels; see "Error events during streaming" below.
- Fix: retry with capped exponential backoff; use `stream: true` when generating very long content; check the upstream time in Latency breakdown in the logs. See [Timeouts and interrupted streams](https://platform.lyrouter.com/ai_gateway/docs/stream-errors/en).

### Configuration 500s (JSON error body)

The following 500s carry a JSON error body (`type` is `api_error`). They mean a platform-side problem with the channel or format-conversion configuration that has nothing to do with the request content, so retrying usually does not help. Note the Request ID and contact support, and switch to another model:

| `error.code` | Error message |
| --- | --- |
| `model_unresolved` | `sync spec needs the upstream model name for the path` |
| `spec_error` | `unusable sync body shape: <reason>`, `unusable async submit spec: <reason>` |
| `unsupported_configuration` | `remove_model is not supported on multipart endpoints` |
| `encode_failed` | `failed to encode request body: <reason>` |

## 503: no available channel, or billing or configuration temporarily unavailable

| Error message | `error.code` | Meaning and fix |
| --- | --- | --- |
| `all routed candidates exhausted` | `no_route_candidate` | No model or channel can serve this request. Common causes are a misspelled `@vmodel/<name>`, a virtual model that does not exist or is disabled, or a model with no usable channel. Check the `model` field; if the name is correct, ask an administrator to check **Model marketplace** and **BYOK** |
| `No configured credential can serve this model` | `no_route_candidate` | The model's channels have no usable credential in this workspace: the platform channel is not enabled, or none of the BYOK credentials are usable; see [BYOK](https://platform.lyrouter.com/ai_gateway/docs/byok/en) |
| `Workspace model projection is unavailable` | `workspace_model_projection_unavailable` | The gateway temporarily cannot read the workspace's model configuration. This is a platform-side problem; retry later |
| `Platform supplier cost snapshot is unavailable`, `BYOK supplier cost snapshot is unavailable` | `supplier_cost_snapshot_not_ready` | The channels that can serve this model do not have prices configured yet, so the gateway cannot bill the call. The gateway tries other channels first and returns this only when none of them is usable. Switch to another model, and tell support the model ID |
| `billing balance is temporarily unavailable` | See the table below | The gateway temporarily cannot confirm the account balance, and rejects the call to avoid charging incorrectly. This is not an insufficient balance |

`billing balance is temporarily unavailable` has `type` `billing_unavailable`; use `error.code` to tell whether it can recover on its own:

| `error.code` | Meaning | Does retrying help |
| --- | --- | --- |
| `billing_maintenance` | The account's billing is being reconciled or under maintenance, and every call under the account is rejected | No; contact support |
| `billing_store_error`, `billing_store_protocol_error`, `byok_release_failed` | A temporary fault in the platform billing service | It may recover later |
| `invalid_balance`, `byok_billing_config_missing`, `byok_reservation_terminal` | Platform-side billing data or configuration is abnormal | No; contact support |

Official SDKs such as OpenAI's and Anthropic's retry 503 automatically by default, but `no_route_candidate`, `billing_maintenance` and `supplier_cost_snapshot_not_ready` do not recover unless the request or configuration changes.

## Errors returned by the upstream provider

The gateway forwards the error status code and response body returned by the upstream provider to you **as is**, and the source in the Status explanation column of the logs shows Upstream.

- **Channels are tried first**: when the upstream returns an error or the connection fails, the gateway retries on the same model's other channels according to the workspace's routing strategy; when all of them fail, you receive the upstream error of the **last** attempt. When a default model is set, a chat request may also be answered by the default model instead; see [Retries and fallback](https://platform.lyrouter.com/ai_gateway/docs/fallback/en).
- **Upstream 401 / 403**: usually means the upstream rejected the credential. With BYOK, confirm with the provider that the API key is valid and has enough quota, then test the connection again in BYOK.
- **`/v1/messages`**: upstream errors are converted to the Anthropic format as well.

### Upstream returns an empty response body

When the upstream returns only an error status code with no response body, the gateway fills in an error body and marks it with `generated_by: "ai-gateway"` as generated by the gateway:

```json
{
  "error": {
    "message": "upstream <provider> returned HTTP 429 for POST <path> with an empty response body",
    "type": "rate_limit_error",
    "code": "upstream_rate_limited",
    "upstream_status": 429,
    "generated_by": "ai-gateway"
  }
}
```

| Upstream status | `error.code` |
| --- | --- |
| 400 | `upstream_invalid_request` |
| 401 | `upstream_unauthorized` |
| 403 | `upstream_forbidden` |
| 404 | `upstream_not_found` |
| 408 | `upstream_timeout` |
| 413 | `upstream_payload_too_large` |
| 429 | `upstream_rate_limited` |
| Other 4xx and 5xx | `upstream_error` |

When the upstream includes its own request ID in the response headers, the error body also has an `upstream_request_id`; use it when you report the issue to the provider.

### Upstream declares an error in a successful response

A few upstreams return HTTP 2xx but declare a failure in the response body, mostly for image and video generation. The gateway rewrites this into an error response: the status code comes from the error the upstream declared, `error.code` is `upstream_rejected` (the upstream rejected the request) or `upstream_no_artifact` (the upstream produced no image or video; carries `upstream_finish_reason`), and `upstream_status` is the upstream's original 2xx status code. These errors are not retried on other channels; adjust the request content according to `error.message`.

## Error events during streaming

All the gateway rejections listed above happen before output starts, so a `stream: true` request also receives an ordinary error response with the matching status code, not an SSE event.

After streaming output has started (the status code is already 200), if the upstream breaks off or times out, the gateway no longer switches channels. Instead, it appends an error event at the end of the stream and then closes the connection:

**OpenAI-compatible endpoints** (Chat Completions, Responses and others):

```text
data: {"error": {"message": "upstream timed out after the response had started", "type": "api_error", "code": "stream_interrupted"}}
```

**Anthropic Messages** (`/v1/messages`):

```text
event: error
data: {"type": "error", "error": {"type": "api_error", "message": "upstream timed out after the response had started"}}
```

- On a timeout, `message` is `upstream timed out after the response had started`; on a connection or read failure, it is `upstream failed after the response had started`.
- Error events that the upstream itself sends in the stream (such as Anthropic's `overloaded_error`) are passed on to you in the protocol you called. The official OpenAI and Anthropic SDKs raise an API error when they receive either kind of error event.
- After an error event, no end marker follows (`data: [DONE]`, `message_stop`, `response.completed`). Treat "no end marker received" as a failed call, and do not treat the partial output you received as a complete result.

Event structures for each protocol are in [Streaming](https://platform.lyrouter.com/ai_gateway/docs/streaming/en); advice on retries and deduplication is in [Timeouts and interrupted streams](https://platform.lyrouter.com/ai_gateway/docs/stream-errors/en).

## Which errors can be retried

| Handling | Errors |
| --- | --- |
| Retry with capped exponential backoff | 500, 504 (HTML); 502 `failed to reach upstream`; 401 `api key verification unavailable`; 403 `api key workspace is unavailable`; 429 `upstream_quota_exhausted`, `upstream_concurrency_full`; 503 `workspace_model_projection_unavailable`, the `billing_store_error` group; streaming `stream_interrupted` |
| Wait for the specified time | 429 `api_key_circuit_open`: wait `Retry-After` seconds; quota 429: wait until after `reset_at` |
| Call again after changing the request or configuration | All 400s, 401 `missing credentials` / `invalid authorization scheme` / `invalid api key`, 402, most 403s, 404, 409; 503 `no_route_candidate`, `supplier_cost_snapshot_not_ready`, `billing_maintenance`; configuration 500s |

Never retry a 4xx automatically without a delay: 402, 403 and 429 responses returned by the gateway all count toward the error-rate circuit breaker, so a program that keeps failing soon gets its key circuit-broken, and the 5th trip is a permanent ban.

## Request identifiers

### The gateway does not return a request ID

LYRouter does not return a request ID in response headers. The `id` in the response body (such as `chatcmpl-...`, `msg_...` or `resp_...`) is the ID of the protocol object, and you cannot search the logs by it. To pin down a call, record the call time, endpoint path, model and API key name on the client, then find the request in the logs.

### Get the Request ID from the log detail

1. Open **Log analysis** in the workspace's left menu. Logs are searchable within a few seconds after a call ends.
2. Filter by Time, API Key, Model, Status and other conditions. Time in the list is when the request finished.
3. Click the row to open Log detail; the value after Request ID at the top identifies this request. Select it and copy it.

You can paste the Request ID into the log search box to find this log again. Account Members see only logs produced by API keys they created. These requests cannot be found in the logs: 401s, 404s for a path that does not exist, requests rejected by the error-rate circuit breaker (429 `api_key_circuit_open`) or by a permanent ban (403 `api_key_permanently_banned`), and `GET /v1/models`. For these, provide the call time, endpoint path and full error message. Full use of the logs is covered in [Troubleshooting with logs](https://platform.lyrouter.com/ai_gateway/docs/logs/en).

### Other identifiers in the logs

| Identifier | Where to find it | Use |
| --- | --- | --- |
| Request ID | Top of the log detail | Uniquely identifies one request; provide it when you contact support |
| Session ID | The tag in the list's Session column (turn it on under **Columns**), shown on hover; filter by Session ID under **Add filter** | See every request of one conversation. Clients can declare a session with the `x-session-id` request header |
| Task ID | The `id` returned by `POST /v1/videos` (starts with `aigwt_`); switch an async task's log detail to Whole task | Query task status and artifacts, and troubleshoot async tasks |
| Upstream request ID | Response headers of ③ Upstream response under Raw packets in the log detail (header names differ by provider, such as `x-request-id`); `upstream_request_id` in error bodies filled in for an empty upstream response; `error.provider_request_id` of a failed video task | Report issues to the upstream provider; with BYOK, contact your own provider |

### What to include when reporting a problem

| Information | Notes |
| --- | --- |
| Request ID | Include it when you can find the request in the logs |
| Call time | To the minute, with the time zone |
| Endpoint and model | Method and path (such as `POST /v1/chat/completions`), the `model` in the request, and whether it was streaming |
| Full error | The HTTP status code and the full response body (including `error.code` and additional fields); for an interrupted stream, the last few events received |
| API key name | The name only |
| Client | Name and version of the tool or SDK; for timeouts, the client's timeout settings; for IP allowlist problems, the egress IP |
| Related identifiers | Task ID, session ID and upstream request ID, if any |

Redact before sending:

- Do not send the API key in plaintext, the full `Authorization` or `x-api-key` request header, or the BYOK keys you hold with providers.
- Delete or replace personal information and business data in prompts, answers and uploaded files; when a minimal reproduction can show the problem, do not attach the original content.
- Content copied from Raw packets in the logs excludes credential headers, but the body may contain sensitive information; check it before sending.
