# Anthropic format

Source: https://platform.lyrouter.com/ai_gateway/docs/anthropic-messages/en
Last updated: 2026-10-19

Generates a model reply from the conversation history, with streaming, image input and tool calls. It is compatible with the Anthropic Messages API: the system prompt goes in `system`, the conversation goes in `messages` made of content blocks, and the response's `content` is also an array of content blocks. The official Anthropic SDKs and Anthropic-protocol clients such as [Claude Code](https://platform.lyrouter.com/ai_gateway/docs/claude-code/en) connect by setting the Base URL to the gateway root, `https://api.lyrouter.com`. The gateway converts between Messages and the upstream protocol, so besides the Claude family, this endpoint can also call the platform's other chat models.

`POST /v1/messages`

## Authentication

Send your API key in a request header, using either of the two forms below:

```text
x-api-key: <API Key>
Authorization: Bearer <API Key>
```

When both are present, only `Authorization` is read; `Bearer` is case-sensitive. The Anthropic SDK's `api_key` parameter sends `x-api-key`, and its `auth_token` parameter sends `Authorization: Bearer`; Claude Code's `ANTHROPIC_AUTH_TOKEN` uses the latter. Create API keys on the workspace's **API Keys** page. Your API key is used only to authenticate with LYRouter and is never forwarded to the upstream provider. See [Base URLs and authentication](https://platform.lyrouter.com/ai_gateway/docs/call-gateway/en).

The Anthropic SDKs and Claude Code append `/v1/messages` themselves, so do not add `/v1` to the Base URL; otherwise the request path becomes `/v1/v1/messages` and returns 404.

## Headers

| Header | Required | Description |
| --- | --- | --- |
| `Content-Type` | No | `application/json` is recommended. Without it, the gateway parses the request body as JSON; any other value (such as `application/x-www-form-urlencoded`) returns 400 |
| `x-api-key` or `Authorization` | Yes | LYRouter API key; see Authentication above for the format. Used only for gateway authentication and never forwarded to the upstream |
| `anthropic-version` | No | Not validated by the gateway. When an Anthropic upstream is called, the channel sets the version, usually `2023-06-01` |
| `anthropic-beta` | No | Forwarded to the upstream as is. When the request contains a cache marker with `"ttl": "1h"` and goes to an Anthropic upstream, the gateway sets it to `extended-cache-ttl-2025-04-11`, replacing the value in the request |
| `x-gateway-passthrough` | No | LYRouter extension header. `require` means the request body must be sent to the Anthropic upstream unchanged, and the gateway returns 400 if it cannot. Native passthrough is currently not enabled on the public gateway, so do not send this header unless you need it; see [Gateway headers and extensions](https://platform.lyrouter.com/ai_gateway/docs/gateway-headers/en#x-gateway-passthrough) |

## Request body

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `model` | string | No | Model ID; you can also write `@vmodel/<name>` ([virtual model](https://platform.lyrouter.com/ai_gateway/docs/presets/en)) or `lyrouter/auto` ([Auto routing](https://platform.lyrouter.com/ai_gateway/docs/auto/en)). When it is omitted, `""` or `null`, the workspace's default model is used; without a default model, the request returns 400. The Anthropic SDKs require it |
| `max_tokens` | integer | Yes | Output limit. The gateway does not reject a request that omits it: for an Anthropic upstream it fills in 4096, and for an OpenAI-compatible upstream it sends nothing and the upstream default applies. Whether thinking counts toward this limit is up to the upstream; Alibaba Cloud Model Studio, for example, does not count it: with qwen3.7-plus set to 16, `output_tokens` is 100 and `stop_reason` is `end_turn` |
| `messages` | array | Yes | Each item is `{"role": "user" or "assistant", "content": a string or an array of content blocks}`; see Content blocks below |
| `system` | string / array | No | System prompt. Either a string or an array of `text` blocks (blocks can carry `cache_control`); multiple blocks are merged into one passage, separated by a blank line |
| `stream` | boolean | No | When `true`, the response is returned as an SSE event stream; see Streaming below |
| `temperature`, `top_p`, `top_k` | number | No | Sent to the upstream as is; whether they take effect depends on the model |
| `stop_sequences` | array | No | Stop sequences. Converted to `stop` for OpenAI-compatible upstreams |
| `tools` | array | No | Custom tools `{"name", "description", "input_schema"}`, which can carry `cache_control`; see Tool calls below |
| `tool_choice` | object | No | `{"type": "auto"}`, `{"type": "any"}` or `{"type": "tool", "name": "..."}`. `{"type": "none"}` and `disable_parallel_tool_use` are not sent to the upstream; if you do not want the model to call tools, do not pass `tools` |
| `thinking` | object | No | `{"type": "enabled", "budget_tokens": 2048}`, `{"type": "adaptive"}` or `{"type": "disabled"}`; for the conversion rules, see Gateway behavior below |
| `metadata` | object | No | Only `user_id` is sent to Anthropic upstreams; nothing is sent to OpenAI-compatible upstreams |
| `service_tier` | string | No | Sent to the upstream as is |
| `context_management`, `output_config` | object | No | Sent only to Anthropic upstreams |

Other fields not listed in the table are not forwarded to the upstream.

### Content blocks

| Block type | Where it appears | Description |
| --- | --- | --- |
| `text` | `system`, `user`, `assistant` | Text; can carry `cache_control` |
| `image` | `user`, inside `tool_result` | `source` is `{"type": "base64", "media_type", "data"}` or `{"type": "url", "url"}`. Converted to `image_url` for OpenAI-compatible upstreams, which requires a model that supports image input; see [Multimodal capabilities](https://platform.lyrouter.com/ai_gateway/docs/multimodal/en) |
| `tool_use` | `assistant` | A tool call made by the model: `id`, `name`, `input` |
| `tool_result` | `user` | A tool's result: `tool_use_id` and `content` (a string, or `text` / `image` blocks) |
| `thinking`, `redacted_thinking` | `assistant` history | Thinking blocks returned in the previous turn; keep `signature` when you send them back |

Blocks of other types (such as `document`) are not forwarded to the upstream.

### Prompt caching

Add `"cache_control": {"type": "ephemeral"}` to a `system` block, a `text` block in a message or a tool definition (for a 1-hour cache, write `{"type": "ephemeral", "ttl": "1h"}`) to mark where a cacheable prefix ends:

| Marker location | Sent to an Anthropic-protocol upstream | Sent to an OpenAI-compatible upstream |
| --- | --- | --- |
| `system` block | Kept; after multiple blocks are merged, the last block with a marker counts | Removed |
| `text` block in a message | Kept | Removed |
| Tool definition in `tools` | Kept | Removed |
| `image` block, `tool_result` block | Not forwarded | Removed |

The part written to the cache is recorded as "Explicit cache write", and later hits are recorded as "Input (cache hit)". OpenAI-compatible upstreams have only the caching the upstream performs automatically and do not produce "Explicit cache write". For how to place markers and improve hit rates, see [Reducing costs and improving cache reuse](https://platform.lyrouter.com/ai_gateway/docs/cost-practice/en).

## Request examples

Install the SDK first: `pip install anthropic` or `npm install @anthropic-ai/sdk`.

**Request examples**

cURL:
```bash
curl https://api.lyrouter.com/v1/messages \
  -H "x-api-key: $LYROUTER_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MODEL_ID",
    "max_tokens": 1024,
    "system": "You are a concise assistant.",
    "messages": [
      { "role": "user", "content": "Explain what an API gateway is in one sentence." }
    ]
  }'
```

Python:
```python
import os
import anthropic

client = anthropic.Anthropic(
    api_key=os.environ["LYROUTER_API_KEY"],
    base_url="https://api.lyrouter.com",
)
message = client.messages.create(
    model="MODEL_ID",
    max_tokens=1024,
    system="You are a concise assistant.",
    messages=[{"role": "user", "content": "Explain what an API gateway is in one sentence."}],
)
print(message.content[0].text)  # content may be empty; see "Response" below
print(message.usage)
```

Node.js:
```js
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  apiKey: process.env.LYROUTER_API_KEY,
  baseURL: "https://api.lyrouter.com",
});
const message = await client.messages.create({
  model: "MODEL_ID",
  max_tokens: 1024,
  system: "You are a concise assistant.",
  messages: [{ role: "user", content: "Explain what an API gateway is in one sentence." }],
});
console.log(message.content[0].text);
console.log(message.usage);
```

Replace `MODEL_ID` with a model ID enabled in your workspace. To use `Authorization: Bearer` instead, in cURL replace the `x-api-key` header with `-H "Authorization: Bearer $LYROUTER_API_KEY"`; in the SDK, replace `api_key` / `apiKey` with `auth_token` / `authToken`.

## Response

A non-streaming request returns a `message` object; an error returns an error object in the Anthropic format. The 200 example below is a typical response from an Anthropic-protocol upstream; the 400 is a request without `model` in a workspace that has no default model; the 401 means the API key is invalid; the 429 means the API key has used up its daily token quota (see [429: rate limits and quotas](https://platform.lyrouter.com/ai_gateway/docs/rate-errors/en)).

**Response examples**

200:
```json
{
  "id": "msg_01HCDu5LRGeP2o7s2xGmxyZa",
  "type": "message",
  "role": "assistant",
  "model": "claude-sonnet-4-5-20250929",
  "content": [
    {
      "type": "text",
      "text": "An API gateway is the single entry point between clients and backend services, handling authentication, routing, rate limiting and monitoring."
    }
  ],
  "stop_reason": "end_turn",
  "usage": {
    "input_tokens": 294,
    "output_tokens": 46,
    "cache_read_input_tokens": 1536,
    "cache_creation_input_tokens": 212
  }
}
```

400:
```json
{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "you must provide a model parameter"
  }
}
```

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

429:
```json
{
  "type": "error",
  "error": {
    "type": "rate_limit_error",
    "message": "rate limit exceeded on apikey total_tokens/day"
  }
}
```

| Field | Description |
| --- | --- |
| `id` | Taken from the identifier the upstream returns. Anthropic upstreams use the `msg_` prefix; other upstreams use different formats, so do not rely on the prefix |
| `model` | The actual model name returned by the upstream, which can differ from the model ID in the request. Corresponds to "Model (actual)" in the logs |
| `content` | A non-streaming response contains only `text` and `tool_use` blocks; to get the thinking content, use streaming. For exceptions, see the notes below the table |
| `stop_reason` | `end_turn`, `max_tokens` or `tool_use`, converted from the upstream's stop reason. Other upstream stop reasons (such as `stop_sequence` and `refusal`) are all returned as `end_turn`. When a tool is specified with `{"type": "tool"}`, it may currently be `end_turn`; see Tool calls below |
| `stop_sequence` | Currently not present in the response, nor in `message_start` or `message_delta` when streaming. Read through the official SDKs, it is `None` in Python and `undefined` in Node.js |
| `usage` | Token usage for this call; see the table below |

- **`content` can be `{}`**: when the upstream outputs only thinking and is cut off by `max_tokens` while still thinking, the `content` of a non-streaming response is currently an empty object `{}` rather than an empty array `[]` (seen on glm-5.3, kimi-k2.7-code and deepseek-v4). Leave enough room in `max_tokens`, and when parsing, treat a `content` that is not an array as empty.
- **`<think>` tags**: some models (such as minimax-m3) write their thinking as `<think>…</think>` inside `text` blocks, both in non-streaming and streaming responses. The gateway does not strip it; remove it yourself if needed.

### usage and the four token types in logs

`usage` maps one to one to the four items under "Token usage" in the logs, and the four fields do not overlap:

| `usage` field | Meaning | Item in the logs |
| --- | --- | --- |
| `input_tokens` | Input that did not hit the cache, **excluding** cache reads and cache writes | "Input (cache miss)" |
| `cache_read_input_tokens` | Input that hit the prompt cache | "Input (cache hit)" |
| `cache_creation_input_tokens` | Input written to the cache in this call; appears only with Anthropic-protocol upstreams | "Explicit cache write" |
| `output_tokens` | Output, including thinking content | "Output" |

The two cache fields are omitted when they are 0. When the upstream uses the OpenAI-compatible protocol, the gateway subtracts the cache hits from the upstream's `prompt_tokens` to get `input_tokens`, matching Anthropic's definition; the total input for the call is `input_tokens + cache_read_input_tokens + cache_creation_input_tokens`. For billing, see [Estimating costs and cache pricing](https://platform.lyrouter.com/ai_gateway/docs/estimate/en).

## Tool calls

Declare the tool in the request:

```json
{
  "model": "MODEL_ID",
  "max_tokens": 1024,
  "tools": [
    {
      "name": "get_weather",
      "description": "Get the current weather for a city",
      "input_schema": {
        "type": "object",
        "properties": { "city": { "type": "string" } },
        "required": ["city"]
      }
    }
  ],
  "messages": [{ "role": "user", "content": "What's the weather in Shanghai right now?" }]
}
```

When the model decides to call the tool, a `tool_use` block appears in `content`, and `stop_reason` is usually `tool_use`:

```json
{
  "type": "tool_use",
  "id": "toolu_01A09q90qw90lq917835lq9",
  "name": "get_weather",
  "input": { "city": "Shanghai" }
}
```

To decide whether to run a tool, check whether `content` contains a `tool_use` block rather than relying only on `stop_reason`: currently, when `tool_choice` is `{"type": "tool", "name": "..."}`, the response carries a `tool_use` block but `stop_reason` is `end_turn`; with `any` or `auto` it is `tool_use`.

After running the tool, put the assistant turn back into `messages` unchanged, then return `tool_result` in a `user` message, with `tool_use_id` matching the call's `id`:

```json
{
  "model": "MODEL_ID",
  "max_tokens": 1024,
  "tools": ["...same as in the first request..."],
  "messages": [
    { "role": "user", "content": "What's the weather in Shanghai right now?" },
    {
      "role": "assistant",
      "content": [
        {
          "type": "tool_use",
          "id": "toolu_01A09q90qw90lq917835lq9",
          "name": "get_weather",
          "input": { "city": "Shanghai" }
        }
      ]
    },
    {
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
          "content": "{\"temp_c\":22,\"condition\":\"cloudy\"}"
        }
      ]
    }
  ]
}
```

The `tools` value in the example is abbreviated; a real request must include the full tool definitions. Keep the following in mind:

- Use only custom tools that have `input_schema`. Anthropic server tools (such as `web_search`) are sent as ordinary custom tools and are not executed on the server.
- In a `user` message that contains `tool_result`, any `text` and `image` blocks next to it are not forwarded. To add notes, put them in the `tool_result`'s `content` or in the next `user` message.
- `is_error` in `tool_result` is not forwarded; when a tool fails, describe the error in `content`.

For a comparison with the Chat Completions format, see [Tool calls](https://platform.lyrouter.com/ai_gateway/docs/tool-call/en).

## Streaming

With `"stream": true`, the response is `text/event-stream`, and each event consists of an `event:` line and a `data:` line:

```text
event: message_start
data: {"type":"message_start","message":{"id":"msg_01HCDu5LRGeP2o7s2xGmxyZa","type":"message","role":"assistant","model":"claude-sonnet-4-5-20250929","content":[],"usage":{"input_tokens":294,"output_tokens":0}}}

event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"An API gateway is"}}

event: content_block_stop
data: {"type":"content_block_stop","index":0}

event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"input_tokens":294,"output_tokens":46,"cache_read_input_tokens":1536,"cache_creation_input_tokens":212}}

event: message_stop
data: {"type":"message_stop"}
```

- **Event order**: `message_start` → for each content block, `content_block_start`, `content_block_delta`, `content_block_stop` → `message_delta` → `message_stop`. `message_stop` marks a normal end.
- **Delta types**: text uses `text_delta`; tool call arguments use `input_json_delta` (the `partial_json` pieces form complete JSON only after they are joined); thinking content uses `thinking_delta`, and an Anthropic upstream's thinking block also has one complete `signature_delta` before `content_block_stop`. Thinking content returned by OpenAI-compatible upstreams is likewise output as `thinking` blocks, but without a signature.
- **Usage**: the complete `usage`, including the cache fields, is in `message_delta`. The `usage` in `message_start` has only `input_tokens` and an `output_tokens` of 0, and when the upstream is OpenAI-compatible, `input_tokens` is 0 as well, so rely on `message_delta`.
- **Errors mid-stream**: if the upstream breaks off after output has started, the gateway sends an `event: error` whose data is `{"type": "error", "error": {"type": "api_error", "message": "upstream timed out after the response had started"}}`, and no `message_stop` follows. Error events sent by the upstream itself (such as `overloaded_error`) are passed to the client in the same format. Treat a stream without `message_stop` as failed; see [Timeouts and interrupted streams](https://platform.lyrouter.com/ai_gateway/docs/stream-errors/en).

**Streaming examples**

cURL:
```bash
curl -N https://api.lyrouter.com/v1/messages \
  -H "x-api-key: $LYROUTER_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{"model":"MODEL_ID","max_tokens":1024,"stream":true,"messages":[{"role":"user","content":"Write a two-line poem"}]}'
```

Python:
```python
import os
import anthropic

client = anthropic.Anthropic(
    api_key=os.environ["LYROUTER_API_KEY"],
    base_url="https://api.lyrouter.com",
)
with client.messages.stream(
    model="MODEL_ID",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Write a two-line poem"}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
    print("\n", stream.get_final_message().usage)
```

Node.js:
```js
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  apiKey: process.env.LYROUTER_API_KEY,
  baseURL: "https://api.lyrouter.com",
});
const stream = client.messages.stream({
  model: "MODEL_ID",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Write a two-line poem" }],
});
stream.on("text", (text) => process.stdout.write(text));
const final = await stream.finalMessage();
console.log("\n", final.usage);
```

For a comparison of streaming formats across protocols, see [Streaming](https://platform.lyrouter.com/ai_gateway/docs/streaming/en).

## Gateway behavior

Internally, the gateway handles all chat requests in the OpenAI Chat Completions format. A `/v1/messages` request is first converted to the internal format, then sent in the protocol of the selected channel: for an Anthropic-protocol channel it is converted back into a Messages request, and for an OpenAI-compatible channel it becomes a Chat Completions request; responses and streaming events are converted back into the Messages format. This conversion happens even when the upstream is Anthropic, so requests and responses are not forwarded byte for byte.

| Request content | Sent to an Anthropic-protocol upstream | Sent to an OpenAI-compatible upstream |
| --- | --- | --- |
| `system` | Merged into one passage of text | Converted to a `system` message |
| `text`, `image` blocks | Kept | Converted to `text` and `image_url` in `content` |
| `tool_use`, `tool_result` | Kept | Converted to `tool_calls` and `tool` messages |
| `tools`, `tool_choice` | Kept (except `none`) | Converted to function tools; `any` becomes `required`, and a named tool becomes a named function |
| `thinking` parameter | `enabled` keeps its budget, `adaptive` is kept, `disabled` is not sent | Converted to `enable_thinking` and `thinking_budget`; removed for upstreams known not to support them |
| Thinking blocks in the history | Signed blocks are sent back unchanged; if the history contains thinking blocks without signatures, all thinking blocks are removed and thinking is turned off for this turn | Removed |
| `max_tokens` | Kept; 4096 is filled in when omitted | Kept; not sent when omitted |
| `stop_sequences` | Kept | Converted to `stop` |
| `metadata` | Only `user_id` is kept | Removed |
| `context_management`, `output_config` | Kept | Removed |
| `cache_control` | See Prompt caching above | Removed |

- **Thinking blocks and switching models**: when a conversation switches midway from a model on an OpenAI-compatible upstream to an Anthropic upstream, the thinking blocks in the history have no signatures, so that turn is sent with thinking turned off to keep the upstream from rejecting the request.
- **Model name**: `model` is replaced with the selected channel's actual model name, and the `model` in the response is the name the upstream returns.
- **Virtual models**: when the request has no `system`, the gateway fills in the virtual model's system prompt; when `system` is present, nothing is injected; see [Virtual models](https://platform.lyrouter.com/ai_gateway/docs/presets/en#when-the-system-prompt-is-injected).
- **Key order**: in the request JSON sent to the upstream, object keys are reordered (array order is unchanged) so identical prefixes reliably hit the upstream cache.
- **Fallback on failure**: when the upstream returns an error or the connection fails before output starts, the gateway switches channels as described in [Retries and fallback](https://platform.lyrouter.com/ai_gateway/docs/fallback/en), and if needed moves on to fallback models and the workspace's default model. Once streaming output has started, the gateway no longer switches channels.
- **Response headers**: the upstream's `Retry-After` is forwarded to the client. Responses carry `anthropic-ratelimit-*` headers: when the upstream returned these headers they are forwarded as is; otherwise, when quotas are configured, they are generated from the gateway quota. For `x-aigw-quota-*`, see [Gateway headers and extensions](https://platform.lyrouter.com/ai_gateway/docs/gateway-headers/en).

## Common errors

Errors on `/v1/messages` are always converted to the Anthropic format. `message` keeps its original text, `error.code` is no longer included, and `error.type` is determined by the status code:

```json
{
  "type": "error",
  "error": {
    "type": "permission_error",
    "message": "Model is not available in the model plaza"
  }
}
```

| HTTP status code | `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` |
| Other 5xx | `api_error` |

| Status code | `error.message` | Cause and fix |
| --- | --- | --- |
| 401 | `missing credentials` / `invalid api key` | No API key was sent, or the key is invalid, deleted or expired. Check `x-api-key` or `Authorization`; see [401 / 403: authentication and access](https://platform.lyrouter.com/ai_gateway/docs/auth-errors/en) |
| 400 | `you must provide a model parameter` | The request has no `model`, and the workspace has no default model set either |
| 400 | `tool_choice 'required' is incompatible with thinking enabled` | With `tool_choice` set to `any` or a specific tool, the upstream rejects the forced call in thinking mode. Use `{"type": "auto"}` instead, or turn thinking off with `thinking: {"type": "disabled"}`. On kimi-k2.7-code turning thinking off has no effect and this error is still returned, so use `auto`; on Alibaba Cloud Model Studio's qwen models turning it off works |
| 403 | `Model is not available in the model plaza` | The model is not enabled in the workspace. Claude Code's background tasks often send requests with official model names; complete the model configuration as described in [Claude Code](https://platform.lyrouter.com/ai_gateway/docs/claude-code/en) |
| 402 | `insufficient credit balance` | The account's Credits balance is insufficient; see [Unavailable models](https://platform.lyrouter.com/ai_gateway/docs/unavailable/en) |
| 429 | `rate limit exceeded on <subject> <metric>/<window>` | A quota or budget is used up. The Anthropic SDK retries 429 automatically, but retrying does not help once a quota is used up; see [429: rate limits and quotas](https://platform.lyrouter.com/ai_gateway/docs/rate-errors/en) |
| 503 | `No configured credential can serve this model` | The model has no usable channel or credential in this workspace; see [Unavailable models](https://platform.lyrouter.com/ai_gateway/docs/unavailable/en) |
| 503 | `all routed candidates exhausted` | No channel can serve this request, for example because `@vmodel/<name>` is misspelled and the virtual model does not exist. `error.type` is `overloaded_error`, so the Anthropic SDK retries automatically, but retrying does not help when the name is wrong. Check the name; see [Unavailable models](https://platform.lyrouter.com/ai_gateway/docs/unavailable/en) |

The full list is in [Errors and request identifiers](https://platform.lyrouter.com/ai_gateway/docs/errors/en). To tell apart specific causes under the same status code, rely on `message`, or check "Status explanation" for the request in the [request logs](https://platform.lyrouter.com/ai_gateway/docs/logs/en). Gateway responses do not include a request ID; when troubleshooting, copy the Request ID from the top of the log details.
