> Raw Markdown twin (generated at build time from the source Markdown). Rendered page: https://docs.gatellm.io/en/reference/error-codes · Doc index: https://docs.gatellm.io/en/llms.txt


# Error code reference

When a call to the gateway returns 4xx / 5xx, come to this page first to locate the meaning and handling action by status code; the error-body formats (OpenAI / Anthropic / Google styles) and the specific sources of 429, 503, and 502 are also here. To trace from symptoms see [Troubleshoot from symptoms](/en/usecases/troubleshooting.md).

## Status codes

| Status code | error_type | Meaning | What to do |
|--------|-----------|------|---------|
| 400 | `bad_request` / `unsupported_feature` | Request JSON parse failed; the requested feature/protocol pair is unsupported; or request-body semantic validation failed (e.g. duplicate `tool_use` ids in the Anthropic request history, see below) | Check the request-body JSON and fields; see [Protocol interop matrix](/en/reference/protocol-matrix.md) for supported protocol pairs; dedupe duplicate `tool_use` ids |
| 401 | `authentication_error` | Access key invalid/disabled, and no anonymous fallback | Verify `Authorization`/`x-api-key` carries a gateway access key that is enabled |
| 403 | `model_access_denied` / `ip_banned` | The key group doesn't authorize that model; or the IP is banned | Check the key's group model / load-balancer list; if IP-banned, wait for the ban timeout |
| 404 | `model_not_found` | Model doesn't exist (or accessed by primary name after `hide_name`) | Verify the model name; a `hide_name` model can only be accessed by alias |
| 413 | `payload_too_large` | Request body exceeds `max_request_size_mb` | Compress the body, or raise `max_request_size_mb` |
| 429 | `rate_limit_error` / `service_unavailable` / `handler_queue_full` / `connection_budget_exceeded` | Rate-limited (per-key / per-IP / upstream), concurrency full, or overload admission rejection (queue depth / connection budget exceeded) | Distinguish by [Sources of 429](#source-of-429) below; admission rejections all carry `Retry-After`, back off and retry per it |
| 500 | `internal_error` / `stream_error` | Internal error, stream error, config error | Check the gateway runtime log; check config |
| 502 | `bad_gateway` / `upstream_not_found` / `lb_degraded` | Upstream failure, upstream not found, load-balancer degradation, translation error, connection error | Check the upstream `base_url` / API key / connectivity (see [Sources of 502](#source-of-502)) |
| 503 | `service_unavailable` / `overloaded_error` / `upstream_saturated` | No available node, stream concurrency cap, global concurrency cap, or all candidate upstreams saturated | See [Sources of 503](#source-of-503); `upstream_saturated` carries `Retry-After` |
| 504 | `timeout` / `outbound_deadline_exceeded` | Request timed out, or the outbound attempt-chain total budget was exhausted | Check whether the upstream is too slow or the timeout config too short; retry if needed |

> **About "unsupported"**: unsupported endpoints/features/protocol pairs return **400** (`unsupported_feature`); a nonexistent model returns **404**. The gateway does not return 501.

> **About tool_use id uniqueness**: the Anthropic Messages contract requires every `tool_use` block's `id` in the history to be globally unique. The gateway validates requests to `/v1/messages` the same way — a history with duplicate `tool_use` ids returns **400**, and the Anthropic client receives `invalid_request_error` with a message like `tool_use ids must be unique: duplicate id "…"`, consistent with calling Anthropic's official API directly. In the response direction, the gateway synthesizes a globally unique `toolu_` id for each `tool_use` block; the client echoes it back as-is, no need to care about the upstream-assigned original id.

## Error response body formats

The error-body format follows the **client protocol**, independent of the upstream:

### OpenAI style (default)

Applies to `/v1/chat/completions`, `/v1/responses`, `/v1/images/*`, `/v1/embeddings`, `/v1/audio/*`, `/v1/rerank`, `/v2/rerank`, `/v3/*`, `/v1/services/*`.

```json
{
  "error": {
    "message": "...",
    "type": "<error_type>"
  }
}
```

The `code` / `param` fields are omitted when null.

### Anthropic style

Applies to `/v1/messages`, `/v1/messages/count_tokens`.

```json
{
  "type": "error",
  "error": {
    "type": "<anthropic_type>",
    "message": "..."
  },
  "request_id": "..."
}
```

`type` mapping:

| HTTP | anthropic type |
|------|----------------|
| 400 | `invalid_request_error` |
| 401 | `authentication_error` |
| 402 | `billing_error` |
| 403 | `permission_error` |
| 404 | `not_found_error` |
| 413 | `request_too_large` |
| 429 | `rate_limit_error` |
| 500 | `api_error` |
| 504 | `timeout_error` |

> Unlisted status codes (e.g. 502 / 503) fall back to `api_error`.

### Google style

Applies to `/v1beta/models/*`, `/v1/models/*`.

```json
{
  "error": {
    "code": 429,
    "message": "...",
    "status": "<grpc_status>"
  }
}
```

`status` mapping:

| HTTP | grpc status |
|------|-------------|
| 400 | `INVALID_ARGUMENT` |
| 401 | `UNAUTHENTICATED` |
| 403 | `PERMISSION_DENIED` |
| 404 | `NOT_FOUND` |
| 413 / 429 | `RESOURCE_EXHAUSTED` |
| 500 | `INTERNAL` |
| 503 | `UNAVAILABLE` |
| 504 | `DEADLINE_EXCEEDED` |

> Unlisted status codes (e.g. 402 / 502) fall back to `INTERNAL`.

## Sources of 429 (different body formats; parsing must be compatible) {#source-of-429}

| Source | Response body | Retry-After header |
|------|--------|--------------|
| Per-access-key rate limit | **Plain text**: `Rate limit exceeded (N requests per Ns). Retry after Ns` | none |
| Per-IP rate limit | JSON: `{"error":{"message":"Rate limit exceeded for IP (N/min)","type":"rate_limit_error"}}` | none |
| Upstream rate limit | JSON (OpenAI/Anthropic/Google style, per client protocol) | yes: `Retry-After: <secs>`, plus `z-rate-limited: <upstream>:<dimension>` |
| Overload admission rejection (queue depth exceeded `handler_queue_full` / data-plane connection budget exceeded `connection_budget_exceeded`) | JSON (OpenAI/Anthropic/Google style, per client protocol) | yes: `Retry-After: <secs>` |

> When parsing 429, callers must be able to parse **both JSON and plain text** (per-key rate limiting is plain text).

> Overload admission rejection is **transient**: the gateway itself is healthy, just too many in-flight requests. Back off per `Retry-After` and retry to recover; persistent occurrence means insufficient capacity, see [Environment variable reference → Memory admission and spill](/en/reference/configuration.md#memory-admission-spill) and [Listening and request limits](/en/reference/configuration.md#listen-and-request-limits).

## Sources of 503 {#source-of-503}

- Streaming concurrency cap reached (`streaming.max_concurrent_streams`, default 200) → 503.
- Global concurrency cap reached (`server.max_global_concurrency`, default 1000; set `0` to disable) → 503 `overloaded_error`.
- No available upstream node (all excluded) → 503.
- All candidate upstreams saturated (`upstream_saturated`) → 503 + `Retry-After`. When an upstream's hung (beyond threshold, still no first response) in-flight requests reach its bad-link budget, that upstream is excluded from candidates; when all candidates are excluded, it fast-fails instead of queuing and dragging down healthy traffic. This is **real-time in-flight counting**: a hung request's slot is released the moment it finishes, and the upstream is usable again as soon as it recovers. In a load-balancing scenario, it first fails over across nodes; only when nodes are exhausted does it return to the client.

> The first two concurrency caps are **not fixed values**; they are TOML config fields (not exposed as environment variables by the official image), see [Configuration reference → Config-file-only fields](/en/reference/configuration.md#config-file-only-fields).

> No credentials and no anonymous fallback returns **401** (see the table above), not 503.

## Sources of 502 {#source-of-502}

- Upstream returned an error, connection error, or translation error.
- Load-balancer degradation (all nodes failed).
- A statically-bound single upstream failed (no cross-node failover).

> With load balancing configured and `retry_on_different_node=true`, a single-node failure auto-fails-over and callers usually don't see 502; persistent 502 means all nodes are unavailable.

## FAQ

**Q: The gateway returned 501?**
It doesn't return 501. Unsupported endpoints/features/protocol pairs return 400 (`unsupported_feature`); a nonexistent model returns 404.

**Q: The 429 response body format isn't fixed?**
Correct: per-key rate limiting returns plain text, per-IP and upstream rate limiting return JSON. When parsing 429, be compatible with both plain-text and JSON bodies.

**Q: 502 won't stop?**
All nodes are unavailable. With load balancing and `retry_on_different_node=true`, a single-node failure auto-fails-over and callers usually don't see 502. Persistent 502 means the upstream is genuinely fully down; check the upstream config and connectivity.

**Q: Is 503 rate limiting or overload?**
Four possibilities: streaming concurrency cap reached, global concurrency cap reached, no available upstream node, all candidate upstreams saturated. Distinguish by the response body's `error.type` (`rate_limit_error` / `service_unavailable` / `overloaded_error` / `upstream_saturated`). No credentials returns 401, not in this list.

**Next**: [Endpoints · auth · protocol interop](/en/reference/endpoints.md) for the full endpoint list; [Client integration and gateway differences](/en/reference/clients-and-gateway-diffs.md) for SDK connection and retry advice; [Protocol interop matrix](/en/reference/protocol-matrix.md) for protocol-pair support.
