ModelSiteAPI

Error codes

Error response shapes, status codes and type values, per-endpoint differences, and which errors are safe to retry

This page is derived from what the code actually does; request and response headers, rate limits and balance handling are covered in Platform extensions. Two things first:

  • A request passes through two layers: the gateway (authentication, rate limits, balance, body checks) and the routing layer (picking an upstream, calling it, failing over). The two layers do not produce errors in exactly the same shape, and the routing layer passes the upstream's status code through to you.
  • error.code is not reliable. Gateway errors have no code; routing-layer errors have a code that sometimes equals type and sometimes is a finer value. Branch on the HTTP status plus error.type, not on code.

Every response carries an X-Request-ID header (echoed back if you sent one). Include it when you report a problem.

How to tell what happened

  1. Look at the status code. 2xx is success; 4xx is usually a problem with the request and should not be retried as-is; only 429 and 5xx are worth retrying (see Retries).
  2. Look at the body shape (next section). Do not assume it is always {"error": {...}}: on /v1/messages it is an Anthropic-style object, and creation-time errors on native endpoints use the vendor's own format.
  3. For streaming requests, look at the stream. A failure after HTTP 200 has been sent only appears inside the stream; the status code does not change. See Failures in the middle of a stream.

Error shapes

OpenAI style (default)

/v1/chat/completions, /v1/responses, /v1/embeddings, images, audio, and every gateway-level error use this shape:

{
  "error": {
    "type": "invalid_request_error",
    "message": "unknown model: foo-1"
  }
}
FieldNotes
error.typeError category, see type values
error.messageHuman-readable text; do not parse it
error.codeAbsent on gateway errors. Present on routing-layer errors, where it may equal type or be a finer value (for example no_upstream_available). The 404 from GET /v1/models/{id} also carries param: null

There is no error.param that points at the offending parameter; read message for the detail.

Anthropic style (/v1/messages and /v1/messages/count_tokens)

{
  "type": "error",
  "error": { "type": "invalid_request_error", "message": "max_tokens is required and must be > 0" }
}

On these two endpoints, gateway errors (authentication, balance, rate limit, oversized body, suspended organization and so on) and routing-layer errors share this one shape, and type uses Anthropic's own names: 400 is invalid_request_error, 401 is authentication_error, 402 is billing_error, 403 is permission_error, 404 is not_found_error, 413 is request_too_large, 429 is rate_limit_error, 503 is overloaded_error (a type that is already an official name is kept), 504 is timeout_error, and any other 5xx is api_error.

Suspended organization or team

When the organization or team is suspended, or the key is not attached to an organization, the status is 403 and the body is the standard error object like every other gateway error, in the shape of the endpoint called (Anthropic style on /v1/messages, OpenAI style elsewhere, Google style on the Gemini endpoint). type is permission_error and message is organization suspended, team suspended or API key is not attached to an organization.

Creation-time errors on native endpoints

On native endpoints only the platform's own parameter validation uses the vendor's native format; every other error (see "Differences per endpoint" below) is still OpenAI style.

EndpointShape of a parameter-validation error
/api/v1/services/aigc/* (DashScope: Wan, HappyHorse, Qwen Image){"code": "InvalidParameter", "message": "..."}; unknown task gives {"code": "NotFound", ...}
/v1/contents/generations/tasks, /api/v3/contents/generations/tasks (Ark Seedance){"error": {"code": "InvalidParameter", "message": "..."}}; unknown task has code NotFound
/v2/video_generation (MiniMax){"type": "error", "error": {"type": "bad_request_error", "message": "...", "http_code": "Bad Request"}}; unknown task has type not_found_error

Status codes

StatusLayerWhenWhat to do
400gatewayNo model can be read from the request (missing field, body is not valid JSON, or a chunked body over the size limit gets this same message); model is not currently callable (unknown model: ...)Fix the body and model name; do not retry
400routingBody binding or validation failed (empty messages, invalid role, missing max_tokens on Anthropic, and so on); message has the reasonFix the request per message
400upstreamThe upstream rejected the request (unsupported parameter, context too long, content moderation)Fix the request per message
401gatewayNo Authorization: Bearer ...; malformed key (must start with sk-ms- or the legacy sk-relay- and be at least 20 characters); key does not exist, was revoked, or expired (these three return the same invalid API key)Check the header and the key's status in the console. The x-api-key header is not accepted
402gatewayOrganization balance exhausted, or a budget envelope (key, member, team) is used up. Once the balance is exhausted, read-only queries (GET /v1/models, video task and response lookups and so on) keep working; only requests that cost money return 402Top up or adjust the budget, then retry
403gatewayOrganization or team suspended, or key not attached to an organization (permission_error); the key's model allow-list does not include the model (model_access_error)Contact your organization admin; do not retry
403upstreamThe upstream returned 403, usually with type: permission_denied_errorNot a problem with your key; contact support with the X-Request-ID
404gatewayGET /v1/models/{id} for a model that does not exist or is unpublished, code: model_not_found; /v1/videos* is retired, code: endpoint_retiredCheck the model name / use the model's native endpoint
404routingThe model exists but has no upstream configured (code: model_not_found); a video or response task ID that does not exist or has expired (not_found; native endpoints use their own formats)Check the ID; tasks have a query window
404frameworkUnknown path or wrong method: the body is the plain text 404 page not found, not JSONCheck the path and method
413gatewayThe declared Content-Length exceeds the limit (gateway default 50 MB, configurable per deployment), invalid_request_error. The nginx in front of the public endpoint has its own 25 MB limit and answers 413 itself with an HTML body, not JSONShrink the body; avoid inlining large files as base64; plan for 25 MB or less
422upstreamThe platform does not generate 422 itself. If an upstream returns it, it is passed through as-isFix the request per message
429gatewayRPM or TPM limit hit (rate_limit_error; message is always too many requests and does not say which limit), with Retry-After; exceeding concurrent Realtime sessions is also 429 (no Retry-After)Back off per Retry-After
429upstreamThe upstream is rate limiting (including a model with a single upstream that is cooling down); carries Retry-After (the upstream's value when given, otherwise 5)Back off by Retry-After
429public entryWhen one source IP sends requests too fast (about 30 per second, bursts of about 100 allowed), the nginx at the public entry answers 429 itself, with an HTML body and no Retry-After; this is separate from the key's RPM limitLower concurrency, back off exponentially
500gatewayThe gateway failed to build the upstream request (server_error)Retry; contact support if it persists
500routingAny other unclassified internal error (internal_error); failure of a video or response task lookup or delete (an upstream 4xx there is also folded into this 500). message is always The request could not be completed; please retry later.; internal text is never echoedSame
502gatewayCannot reach the routing layer, or no response headers from it within 120 seconds (server_error, message: upstream service unavailable)Retry; for long non-streaming requests use stream: true
502routingNetwork-level failure to the upstream (connect, DNS, TLS, timeout): type: bad_gateway, message is always The upstream service is temporarily unavailable. Please retry.Retry
503gatewaySee "Where a 503 comes from" belowSee below
503routingNo usable upstream: code: no_upstream_available; message is always The upstream service is temporarily unavailable. Please retry later.; carries Retry-After (default 5)Retry later
408, 504, and othersupstreamThe upstream's own status. embeddings and rerank fold an upstream 408/504 into 408 timeout_error; other endpoints pass the status through unchangedRetry

Errors the routing layer returns for an upstream failure come from the last upstream it tried: the platform switches upstreams first, and when all fail it returns the last one's status and message. So the same 401, 402 or 403 can be either the gateway's verdict on your key or an upstream's verdict on the platform's own account. Gateway 401 messages are one of a few fixed sentences (see the table), and a gateway 402 always has type: insufficient_quota (billing_error on /v1/messages); anything that does not match is an upstream pass-through, covered below.

Where a 503 comes from

typecodemessage looks likeMeaningRetry-After
overloaded_errornonegateway at capacity, retry laterThe gateway's concurrency cap is full (default 500 in-flight upstream requests, queueing up to 10 seconds)yes, fixed 5
service_unavailablenoneservice temporarily unavailable, please retry shortlyThe gateway cannot currently guarantee that key revocations and suspensions take effect in time, so it refuses to serve (returned before authentication)yes, fixed 5
api_errornonethe platform cannot evaluate this key right nowThe gateway lacks the data it needs to decide on this key (just started, or sync incomplete)no
service_unavailableno_upstream_availableThe upstream service is temporarily unavailable. Please retry later.The routing layer cannot pick an upstreamyes, default 5 (the routing layer's own wait when it has one)
the upstream's own typevariesthe upstream's textThe upstream returned 503 (an Anthropic-type upstream's 529 also becomes 503)no

All of these are worth retrying, but only the first states how long to wait.

type values

Generated by the gateway

typeStatusNotes
authentication_error401Key missing, malformed or invalid
model_access_error403The model is not in the allow-list of the key's organization or team
insufficient_quota402Balance or budget envelope too low (billing_error on /v1/messages); message is insufficient balance, with (budget envelope: ...) appended when an envelope tripped
rate_limit_error429RPM / TPM limit, or too many concurrent Realtime sessions
invalid_request_error400 / 404 / 413Missing model, unknown model, oversized body, retired path
overloaded_error503Gateway concurrency cap full
service_unavailable503Configuration feed stalled
api_error503Gateway cannot decide on this key
server_error500 / 502Fault between the gateway and the routing layer

Generated by the routing layer

typeStatuscodeNotes
invalid_request_error400same as typeBody binding or validation failed
invalid_request_error404model_not_foundThe model has no upstream
service_unavailable503no_upstream_availableNo usable upstream
service_unavailable503upstream_cooling_downAll usable upstreams are in temporary cooldown; retry shortly
bad_gateway502bad_gatewayNetwork-level upstream failure
internal_error500internal_errorUnclassified internal error
not_found404same as typeResponse or video task ID not found

The no_upstream_available and upstream_cooling_down codes were renamed on 2026-10-19. The old names no_suppliers_available and all_vendors_cooling_down mean the same thing; if your client branches on the old code, match both the old and the new name.

Upstream errors passed through the routing layer

The status is the upstream's; message is the upstream's text (with request IDs, internal endpoint IDs and similar identifiers stripped). type depends on the endpoint and the upstream family:

EndpointUpstream statustype
chat / messages / responses, OpenAI-compatible upstreams (most domestic vendors)anyapi_error, status unchanged
chat / messages / responses, Anthropic-type upstreams400 401 403 404 429 500invalid_request_error authentication_error permission_denied_error not_found_error rate_limit_error internal_server_error; 529 becomes 503 service_unavailable; anything else api_error
image and video creation (the DashScope and Ark families)400 401/403 404 429 500invalid_request_error authentication_error (note that 403 is also mapped to 401) not_found_error rate_limit_error internal_server_error; anything else api_error
embeddings, rerankby status and message textrate_limit_error, context_length_exceeded, content_policy_violation, invalid_request_error, authentication_error, permission_denied_error, not_found_error, timeout_error (408), internal_server_error, service_unavailable, api_error

context_length_exceeded and content_policy_violation only appear on embeddings and rerank. On conversational endpoints, an over-long context or a moderation block comes back as 400 with api_error or invalid_request_error; the reason is in message.

Differences per endpoint

EndpointGateway errorsRouting / upstream errorsMid-stream failure
/v1/chat/completionsOpenAI styleOpenAI style, with codedata: {"error":{"message","type"}} then data: [DONE]
/v1/messages, /v1/messages/count_tokensAnthropic styleAnthropic styleevent: error with {"type":"error","error":{"type","message"}}
/v1/responsesOpenAI styleOpenAI style, with codeevent: error ({"type":"error","code","message"}) followed by event: response.failed
/v1/embeddings, /v1/rerank, images, audioOpenAI styleOpenAI style, with codenot applicable
Video creation and task polling (DashScope, Ark, MiniMax native endpoints)OpenAI styleParameter validation and unknown task use the native format; no upstream available or an upstream call failing is still OpenAI stylenot applicable

Things to know about video and other asynchronous tasks:

  • A successful create call only means the task was submitted, with HTTP 200. If the task fails later, polling still returns 200: on the DashScope endpoints output.task_status is FAILED and the reason is in output.code and output.message; on the Ark and MiniMax endpoints the upstream's task body is returned with status set to failed, and the error fields are the upstream's own.
  • A task ID that is invalid or expired on poll gives the native endpoint's own 404 format; an error from the upstream call itself during a poll is an OpenAI-style 500 or 502.
  • /v1/videos and /v1/videos/generations are retired and return 404 with code: endpoint_retired. Video is served only on each model's native endpoint.

GET /v1/responses/{id} locates the upstream that created the response from the ID. An ID that cannot be decoded gives 400; an upstream that cannot be found gives 404 (type: not_found). DELETE /v1/responses/{id} and POST /v1/responses/{id}/cancel need no body and no model; the gateway passes them straight through. They cost nothing, so they keep working when the balance is exhausted.

Failures in the middle of a stream

Once a stream has started, the HTTP status is already 200, so later failures can only be written into the stream. Three cases:

  • The upstream errors mid-stream, the connection drops, the stream is truncated (it ended without a terminal event), or no data arrives for 4 minutes by default: an error event is emitted in the endpoint's format from the table above, then the stream ends. type is the upstream's error type, and server_error for network-level failures and truncation.
  • The organization's balance runs out while the stream is in progress: the gateway cuts the stream and writes data: {"error":{"type":"insufficient_quota","message":"balance exhausted"}} followed by data: [DONE]. This frame uses the OpenAI shape on every streaming endpoint, including /v1/messages.
  • You disconnect: no error event is sent.

How to handle it: treat "no normal terminator received" as a failure. For Chat Completions that means the last chunk has no finish_reason or an error frame appeared; for Anthropic, no message_stop; for Responses, no response.completed. On a mid-stream failure the part already generated is still billed at the upstream's actual usage.

If the failure happens before the first byte, the platform switches upstreams automatically. If one succeeds you get a normal response; only when all fail do you get a JSON error with a status code.

Retries

StatusRetry?Notes
400 401 402 403 404 413NoRetrying unchanged gives the same result. 402 clears only after a top-up
429YesHonor Retry-After if present, otherwise back off exponentially. Retry-After can be shorter than you actually need to wait when the TPM limit tripped; leave margin
500 502 503 504 408YesExponential backoff with jitter, a few attempts at most
401 402 403 with type: api_errorNoThis is an upstream's verdict on the platform's account, not your key or balance. Retrying does not help; contact support with the X-Request-ID
import random, time

RETRYABLE = {408, 429, 500, 502, 503, 504}

def call_with_retry(send, tries=4):
    for i in range(tries):
        resp = send()
        if resp.status_code not in RETRYABLE or i == tries - 1:
            return resp
        wait = float(resp.headers.get("Retry-After", 2 ** i))
        time.sleep(wait + random.random())

Duplicate calls and double billing

  • The platform has no idempotency key and does not deduplicate by request content.
  • When the platform switches upstreams internally, only the attempt that finally succeeds is billed; failed attempts are not. You see one success or one error.
  • A retry from your side is a new request. If the earlier one actually succeeded and only the response failed to reach you (client timeout, dropped connection), the retry produces a second generation and a second charge. That is tolerable for conversational requests; it is not for video creation: after a timeout, 502 or dropped connection on a create call, first check whether the task was created before creating another, or you end up with two tasks and two charges.
  • Do not retry a stream that failed midway as if it were the same generation: the output already delivered has been billed, and starting over bills again. To continue, put what you received into the context and send a new request.
  • Non-streaming requests face two related time limits: the gateway disconnects and returns 502 if it has no response headers after 120 seconds, and the routing layer's total budget for an upstream call is 10 minutes non-streaming and 5 minutes streaming. Use stream: true for requests that may take longer than two minutes.

On this page