Docs
Get an API key
Concepts›Errors

Errors

A request that fails answers with an HTTP status and a JSON error body. Every route uses OpenRouter's error shape, except /v1/messages, which uses Anthropic's so their SDKs and Claude Code can read it.

View .md

The error body

Every route but one answers errors in OpenRouter's shape. code repeats the HTTP status, message says what went wrong in words you can show a person, and metadata.error_type names the kind of error for code to branch on. type is there for OpenAI's SDKs, which read it.

JSON
{
  "error": {
    "code": 400,
    "message": "Input for property `seed` must be a valid integer value",
    "type": "validation_error",
    "metadata": {
      "error_type": "invalid_request"
    }
  }
}

/v1/messages answers in Anthropic's shape instead, with Anthropic's type names:

JSON
{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "max_tokens is required and must be an integer of at least 1"
  }
}

Messages are written to be read. A validation error names the field it is about, and an error from the model's engine is cut to its first line, without the engine's internals.

Status codes

Status What it means Retry?
400 A field is missing, malformed or not supported by the model. The message names it. No: fix the request
401 The API key is missing, wrong or not active. No
404 No such model, job, file or route. An unknown route's message names its method and path. No
405 The route exists, but not for this method. Allow lists the methods it takes. No
409 A job changed state while you acted on it. Read the job again first
413 The body or a file is larger than the route accepts. The message gives the limit. No
429 Too many requests for this model. Yes, after Retry-After
500 Something failed on our side. Once, after a pause
502 The model's engine, or a service the request needs, could not be reached. Yes, with a growing wait
503 The model is unavailable or busy right now. Yes, with a growing wait
504 The engine did not answer in time. Yes, with a growing wait

Type names

Each error also names its kind, for code that branches on it: metadata.error_type and type in the usual envelope, and Anthropic's type on /v1/messages.

Status metadata.error_type type Anthropic type
400 invalid_request validation_error invalid_request_error
401 authentication auth_error authentication_error
404 not_found not_found_error not_found_error
405 invalid_request invalid_request_error invalid_request_error
409 invalid_request invalid_request_error
413 payload_too_large invalid_request_error request_too_large
429 rate_limit_exceeded rate_limit_error rate_limit_error
500 server server_error api_error
502 provider_unavailable server_error api_error
503 provider_overloaded server_error api_error
504 timeout timeout_error api_error

A few errors carry a more specific metadata.error_type than their status: a video or song request that sends a field its model does not take is a 400 with unsupported_parameter.

Validation errors are 400 on every route. The one exception is /v1/systemone, which answers 422 as TypeSafe's API does.

When a stream fails

A streamed request that fails before its first token answers with the real status and the error body above, so it can be handled like any other error.

Once a stream has started, its status has already been sent as 200, so a failure arrives in the stream:

Chat completions send one last chunk with the error, finish_reason: "error", then data: [DONE]:

JSON
{
  "id": "chatcmpl-…",
  "object": "chat.completion.chunk",
  "created": 1790000000,
  "model": "deepseek-v4.1-flash",
  "error": {
    "code": 504,
    "message": "The AI service is taking too long to respond. Please try again later.",
    "type": "timeout_error",
    "metadata": {
      "error_type": "timeout"
    }
  },
  "choices": [
    {
      "index": 0,
      "delta": {
        "content": ""
      },
      "logprobs": null,
      "finish_reason": "error",
      "native_finish_reason": null
    }
  ]
}

Responses (/v1/responses) send an error event, then response.failed with the response and its error.

Messages (/v1/messages) send an error event with Anthropic's api_error, then message_stop.

Treat text already received as incomplete when a stream ends this way.

Retrying

How to wait, for the statuses the table marks as worth retrying:

  • 429: as many seconds as Retry-After says. A request over the number a model runs at once waits its turn before it is refused, so a 429 can arrive after a pause.
  • 502, 503, 504: start at a second or two and double the wait each time.
  • 500: once. If it fails again, the request is likely to keep failing.

Limits has a retry helper in Python and TypeScript.

A job created by POST /v1/videos or POST /v1/music that fails later is not an HTTP error: the job's status becomes failed and its error says why. See Async jobs.