# Common errors

https://modelportal.ai/docs/errors

When a call fails, check the HTTP status and error code, then use the tables below. Error messages follow the `Accept-Language` header (`zh-CN` or `en-US`).

## Error formats

| API | Format |
| --- | --- |
| OpenAI-compatible `/v1/*` | `{"error": {"message", "type", "code", "request_id"}}` |
| Anthropic-compatible | `{"type": "error", "error": {"type", "message"}}`, with no machine-readable code |
| Gemini-compatible | `{"error": {"code", "message", "status"}}`, where `code` is the HTTP status; there is no platform error code |
| Platform API `/api/v1/*`, media tasks | `{"code", "message", "request_id", "details"}` |

Include the `request_id` when contacting support. Once a call has gone to an upstream, the `X-ModelPortal-Request-Id` response header carries its request ID, which you can look up in the console's call details.

## Error codes

| Code | HTTP | Cause | What to do |
| --- | --- | --- | --- |
| `invalid_request` | 400 | The body or a parameter is invalid, including unknown fields and nonexistent provider or endpoint identifiers in `provider`; the message names the field | Fix the request as the message says |
| `unsupported_parameter` | 400 | No candidate endpoint supports a parameter or capability in the request, including turning reasoning off on models that cannot; the message lists the parameters | Drop the parameter or use another model |
| `stateful_not_supported` | 400 | `/v1/responses` carried `previous_response_id`, `conversation` or `background`; stateful conversations are not supported yet | Keep the context yourself and send the full input each time |
| `invalid_grant` | 400 | The app authorization code is invalid, expired or used, or the key can no longer be created | Start the authorization again |
| `invalid_api_key` | 401 | Missing, wrong or revoked key, or a key put in the URL of the OpenAI or Anthropic API | Check the key and the request header |
| `key_inactive` | 401 | The key is paused or expired, or its app is disabled | Resume the key or create a new one |
| `insufficient_balance` | 402 | Balance cannot cover the call's estimated cost, or the account is in arrears | Top up |
| `budget_exceeded` | 402 | The key hit its daily or monthly spend limit | Raise the limit or wait for the period to roll over |
| `key_restricted` | 403 | The model is not in the key's allowed models, the source IP is not on the key's allowlist, or this kind of key cannot use the endpoint | Update the key's allowed models or IP allowlist |
| `request_blocked` | 403 | The request was blocked by risk control, or the account is frozen, API-paused or banned. Content that hits a content rule, and requests with images while image moderation is unavailable, also get this code; the message ends with the category, such as `content.prohibited.violence` or `content.moderation_unavailable` | Check notifications or open a ticket; for blocked content, change it or appeal |
| `region_restricted` | 403 | The request comes from a region that is not supported | The service is not available there; open a ticket if this looks wrong |
| `verification_required` | 403 | Identity verification is needed first | Complete verification in the console |
| `model_not_found` | 404 | Unknown, in maintenance, or lacking the capability this endpoint needs | Check the model ID and [capabilities](/docs/capabilities) |
| `model_retired` | 410 | The model is retired; the message names the replacement when one is set | Switch to the replacement model |
| `no_eligible_endpoint` | 422 | No endpoint meets the constraints; the body carries an `excluded` list | Adjust constraints using the exclusion reasons below, or retry later |
| `rate_limited` | 429 | The account or key hit its requests-per-minute or concurrent-request limit; carries `Retry-After` | Back off and retry, or wait for running requests to finish |
| `capacity_exhausted` | 429 | Every candidate endpoint is at capacity (at its declared limit, or in the short cooldown after an upstream 429), or every attempt got an upstream 429; carries `Retry-After` | Retry after `Retry-After` |
| `upstream_failed` | 502 | Every permitted attempt failed, and not all of them with an upstream 429; the body carries `attempts` | Retry later; failed calls are free |

These codes are outside the standard set above but can also appear:

| Code | HTTP | Cause | What to do |
| --- | --- | --- | --- |
| `request.too_large` | 413 | The request body exceeds the size limit | Shrink the body; upload large files first |
| `request.endpoint_moved` | 410 | A retired address was used, such as anything under `/anthropic/*` | Use the new address given in the message |
| `upstream.unavailable` | 503 | A service the platform depends on (such as rate limiting) is unavailable | Retry later |

## Failed attempts

`upstream_failed`, and `capacity_exhausted` caused by upstream 429s, carry `attempts` in the error object, one item per attempt in order:

```json
{"attempts": [{"provider": "PROVIDER_ID", "endpoint": "ENDPOINT_ID", "status": 503, "cost": 0.000000}]}
```

`provider` is the provider identifier, `endpoint` the endpoint identifier, `status` the upstream HTTP status (0 when the connection failed or timed out), and `cost` is 0 for failed attempts. When every attempt got an upstream 429 the call returns 429 `capacity_exhausted`, and `Retry-After` is the earliest time any attempt recovers: the upstream's `Retry-After` when it sent one, otherwise the end of the short cooldown.

## Exclusion reasons for 422

The body of `no_eligible_endpoint` carries an `excluded` list naming each endpoint that passed your constraints but was excluded, with a reason code:

```json
{"excluded": [{"offering_id": "route_xxxxxxxx", "reason": "unavailable"}]}
```

| Reason code | Meaning |
| --- | --- |
| `unavailable` | The provider is not live, the access point is not ready, or the endpoint is neither Available nor Degraded (for example paused or in maintenance) |
| `region` | The endpoint does not serve the request's source region |
| `capability` | The endpoint lacks a capability or parameter the request uses |
| `context` | The estimated input (plus any output limit you set) exceeds the endpoint's context length |
| `circuit_open` | The endpoint's circuit breaker is open or half-open |
| `upstream_cooldown` | The endpoint is in a short cooldown after an upstream 429 |
| `capacity` | The endpoint reached its declared RPM or TPM in the current 60-second window |
| `session` | The request carries a signed thinking block that only the signing endpoint can serve, and it is unavailable |

If your own constraints (`only`, `ignore`, `data_collection`, `quantizations`, `max_price`, `data_regions`, `exclude_regions`) exclude every endpoint up front, `excluded` is an empty array and the message names the constraints. Endpoints that were never shown publicly are not listed.

Two exceptions: when parameters or capabilities alone leave no endpoint, you get 400 `unsupported_parameter`; when capacity or an upstream 429 cooldown alone leaves none, you get 429 `capacity_exhausted` with `Retry-After` set to the earliest recovery.

## Rate limits

Each account has three limits: requests per minute for the account, requests per minute for each key, and requests in progress at the same time. Defaults go up with the account's total top-ups:

| Total top-ups | Account requests per minute | Requests per minute per key | Requests in progress |
| --- | --- | --- | --- |
| From 0 | 600 | 600 | 5 |
| From 50 USD | 1,200 | 1,200 | 10 |
| From 500 USD | 3,000 | 3,000 | 30 |

Going over any of them returns 429 `rate_limited`, and the `Retry-After` header gives the suggested wait in seconds. Your current limits are on the API keys page of the console. Open a ticket if you need higher limits.

## Retrying

- 429, 502 and 503 are retryable; use exponential backoff with a retry cap.
- 400, 401, 402, 403, 404, 410 and 422 will not succeed on retry; fix the cause first.
- Before the first content event, all attempts share a 600-second total timeout; once output starts, a gap of more than 120 seconds between events closes the stream. Use streaming for long outputs.

## FAQ

### How should I retry a 429?

rate_limited is retryable and covers both rate and concurrency limits. Use exponential backoff with a retry cap and reduce concurrent requests.

### Am I charged for upstream_failed?

No. Calls that fail or time out upstream count as failed and are free; retry later.

### Why is there no error code in the Anthropic format?

Anthropic error bodies carry only a type and message. Use the OpenAI-compatible API if you need machine-readable codes.

