Errors and request identifiers
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:
{
"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:
{
"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:
# -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"}]}'
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)
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 |
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.
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.
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. 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.
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.
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.
| 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 |
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 |
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.
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>orlyrouter/autoon 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.
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.
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. 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. 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), 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.
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>isapikey,member,deptorws;<metric>isrequests,total_tokens,prompt_tokens,completion_tokensorcredits;<window>isminute,minutes,day,monthortotal(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 iscredits,tokens,requests,input-tokensoroutput-tokens;/v1/messagesalso carriesanthropic-ratelimit-*, but credits have no matching header, so when only Credits quotas are configured the gateway does not generateanthropic-ratelimit-*. - Retry-After: set only by the circuit breaker (
api_key_circuit_open), together withX-RateLimit-Reason: api_key_circuit_openandX-RateLimit-Reset(Unix seconds); quota 429s do not carry it. The upstream's ownRetry-Afteris 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 and Budgets, quotas and rate limits.
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, 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: truewhen generating very long content; check the upstream time in Latency breakdown in the logs. See Timeouts and interrupted streams.
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 |
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.
- 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:
{
"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):
data: {"error": {"message": "upstream timed out after the response had started", "type": "api_error", "code": "stream_interrupted"}}
Anthropic Messages (/v1/messages):
event: error
data: {"type": "error", "error": {"type": "api_error", "message": "upstream timed out after the response had started"}}
- On a timeout,
messageisupstream timed out after the response had started; on a connection or read failure, it isupstream 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; advice on retries and deduplication is in Timeouts and interrupted streams.
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
- Open Log analysis in the workspace's left menu. Logs are searchable within a few seconds after a call ends.
- Filter by Time, API Key, Model, Status and other conditions. Time in the list is when the request finished.
- 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.
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
Authorizationorx-api-keyrequest 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.