[metadata]
apple-mobile-web-app-title: OpenRouter | Documentation
application-name: OpenRouter | Documentation
description: API Errors and Debugging
generator: Mintlify
msapplication-TileColor: #7624F4
msapplication-config: /docs/_mintlify/favicons/openrouter-d02e98a0/EmhfYwFO2tvnkuV-/_generated/favicon/browserconfig.xml
og:description: Learn how to handle errors and debug requests in OpenRouter API interactions. Comprehensive guide to error codes, messages, debugging options, and best practices.
og:image: https://openrouter.ai/dynamic-og?title=API%20Error%20Handling%20and%20Debugging&description=Learn%20how%20to%20handle%20errors%20and%20debug%20requests%20in%20OpenRouter%20API%20interactions.
og:image:height: 630
og:image:width: 1200
og:site_name: OpenRouter Documentation
og:title: API Error Handling and Debugging - Complete Guide
og:type: website
og:url: https://openrouter.ai/docs/api_reference/errors-and-debugging
twitter:card: summary_large_image
twitter:description: Learn how to handle errors and debug requests in OpenRouter API interactions. Comprehensive guide to error codes, messages, debugging options, and best practices.
twitter:image: https://openrouter.ai/dynamic-og?title=API%20Error%20Handling%20and%20Debugging&description=Learn%20how%20to%20handle%20errors%20and%20debug%20requests%20in%20OpenRouter%20API%20interactions.
twitter:image:height: 630
twitter:image:width: 1200
twitter:title: API Error Handling and Debugging - Complete Guide
viewport: width=device-width, initial-scale=1, viewport-fit=cover

[canonical-links]
https://openrouter.ai/docs/api_reference/errors-and-debugging

[document-links]
/docs/llms.txt: /docs/llms.txt
API Changelog: /docs/changelog
API Reference: /docs/api_reference/overview
API Versioning: /docs/api_reference/versioning
Add credits: https://openrouter.ai/credits
Agent SDK: /docs/agent-sdk/overview
Apps: https://openrouter.ai/apps
Authentication: /docs/api_reference/authentication
Benchmarks: https://openrouter.ai/benchmarks
Chat: https://openrouter.ai/chat
Client SDKs: /docs/client-sdks/overview
Cookbook: /docs/cookbook/get-started/quickstart
Docs: /docs/quickstart
Docs: https://openrouter.ai/docs
Embeddings: /docs/api_reference/embeddings
Errors and Debugging: /docs/api_reference/errors-and-debugging
Limits: /docs/api_reference/limits
Models: https://openrouter.ai/models
OpenRouter | Documentation home page: https://openrouter.ai
Overview: /docs/api_reference/overview
Overview: /docs/api_reference/responses/overview
Parameters: /docs/api_reference/parameters
Pipeline Stages: /docs/guides/features/router-metadata#pipeline-stages
Rankings: https://openrouter.ai/rankings
Streaming: /docs/api_reference/streaming
fallback routing: /docs/guides/routing/provider-selection
guardrail: /docs/guides/features/guardrails
guardrails: /docs/guides/features/guardrails
privacy settings: https://openrouter.ai/settings/privacy
router metadata: /docs/guides/features/router-metadata

[structured-data]
{"@context":"https://schema.org","@graph":[{"@id":"https://openrouter.ai/#organization","@type":"Organization","logo":{"@type":"ImageObject","url":"https://mintcdn.com/openrouter-d02e98a0/ksNSeB_K7gD-BUDh/assets/logo-v2-light.svg?fit=max&auto=format&n=ksNSeB_K7gD-BUDh&q=85&s=690e45873843519efe02bcbe45795dac"},"name":"OpenRouter | Documentation","url":"https://openrouter.ai"},{"@id":"https://openrouter.ai/docs#website","@type":"WebSite","name":"OpenRouter | Documentation","publisher":{"@id":"https://openrouter.ai/#organization"},"url":"https://openrouter.ai/docs"},{"@id":"https://openrouter.ai/docs/api_reference/errors-and-debugging#webpage","@type":"WebPage","breadcrumb":{"@id":"https://openrouter.ai/docs/api_reference/errors-and-debugging#breadcrumb"},"dateModified":"2026-08-20T02:29:13.005Z","description":"API Errors and Debugging","isPartOf":{"@id":"https://openrouter.ai/docs#website"},"name":"Errors and Debugging","url":"https://openrouter.ai/docs/api_reference/errors-and-debugging"},{"@id":"https://openrouter.ai/docs/api_reference/errors-and-debugging#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","item":"https://openrouter.ai/docs/api_reference/overview","name":"API Guides","position":1},{"@type":"ListItem","item":"https://openrouter.ai/docs/api_reference/errors-and-debugging","name":"Errors and Debugging","position":2}]},{"@id":"https://openrouter.ai/docs/api_reference/errors-and-debugging#article","@type":["Article","TechArticle"],"dateModified":"2026-08-20T02:29:13.005Z","description":"API Errors and Debugging","headline":"Errors and Debugging","image":"https://openrouter.ai/dynamic-og?title=API%20Error%20Handling%20and%20Debugging&description=Learn%20how%20to%20handle%20errors%20and%20debug%20requests%20in%20OpenRouter%20API%20interactions.","isPartOf":{"@id":"https://openrouter.ai/docs#website"},"mainEntityOfPage":{"@id":"https://openrouter.ai/docs/api_reference/errors-and-debugging#webpage"},"name":"Errors and Debugging","publisher":{"@id":"https://openrouter.ai/#organization"},"url":"https://openrouter.ai/docs/api_reference/errors-and-debugging"}]}

[content]
API Error Handling and Debugging - Complete Guide
Documentation Index
Fetch the complete documentation index at:
/docs/llms.txt
Use this file to discover all available pages before exploring further.
Skip to main content
OpenRouter | Documentation
home page
Search...
⌘
K
Ask Assistant
Models
Benchmarks
Chat
Rankings
Apps
Docs
Search...
Navigation
API Guides
Errors and Debugging
Docs
API Reference
Client SDKs
Agent SDK
Cookbook
API Guides
Overview
Streaming
Embeddings
Limits
Authentication
Parameters
Errors and Debugging
Responses API
Versioning
API Versioning
API Changelog
API Reference
Analytics
TTS
STT
OAuth
Benchmarks
BYOK
Chat
Classifications
Credits
Datasets
Embeddings
Endpoints
Generations
Guardrails
Images
API Keys
Anthropic Messages
Models
Observability
Organization
Presets
Providers
Rerank
Responses
SCIM
Video Generation
Workspaces
On this page
Error codes
Retry-After header
Moderation errors
Guardrail errors
Provider errors
Masking and raw provider details
When no content is generated
Streaming error formats
Pre-stream errors
Mid-stream errors
Typed error codes
Token and length limits
Authentication and authorization
Rate limiting and availability
Request validation
Content policy
Image errors
Generic
Model availability errors
Availability codes
The availability object
Retrying with retry_after
Using fallback_models suggestions
Example availability error payloads
Skin-specific error formats
Chat Completions (/api/v1/chat/completions)
Responses API (/api/v1/responses)
Error code transformations
Anthropic Messages (/api/v1/messages)
Debugging
Debug option shape
Usage
Chat Completions
Responses API
Debug response format
Chat Completions
Responses API
Important notes
Use cases
Privacy and redaction
API Guides
Errors and Debugging
Copy page
Copy page
API Errors and Debugging
Copy page
Copy page
For errors, OpenRouter returns a JSON response with the following shape:
type
ErrorResponse
=
{
error
:
{
code
:
number
;
message
:
string
;
metadata
?:
Record
<
string
,
unknown
>;
};
};
The HTTP Response will have the same status code as
error.code
, forming a request error if:
Your original request is invalid
Your API key/account is out of credits
Otherwise, the returned HTTP response status will be
and any error occurred while the LLM is producing the output will be emitted in the response body or as an SSE data event.
Example code for printing errors in JavaScript:
const
request
=
await
fetch
(
'https://openrouter.ai/...'
);
console
.
log
(
request
.
status
);
// Will be an error code unless the model started processing your request
const
response
=
await
request
.
json
();
console
.
error
(
response
.
error
?.
code
);
// Will be an error code
console
.
error
(
response
.
error
?.
message
);
​
Error codes
: Bad Request (invalid or missing params, CORS)
: Invalid credentials (OAuth session expired, disabled/invalid API key)
: Your account or API key has insufficient credits. Add more credits and retry the request.
: Forbidden (insufficient permissions, guardrail block, or moderation flag)
: Your request timed out
: You are being rate limited
: Your chosen model is down or we received an invalid response from it
: There is no available model provider that meets your routing requirements
​
Retry-After header
On
and
responses, OpenRouter may include a standard HTTP
Retry-After
response header indicating how many seconds to wait before retrying.
HTTP
/
1.1
429
Too Many Requests
Retry-After
:
60
The OpenAI SDK, Anthropic SDK, Vercel AI SDK, and OpenRouter SDK already respect this header for backoff. If you’re using
fetch
directly, honor it before retrying:
const
res
=
await
fetch
(
'https://openrouter.ai/api/v1/chat/completions'
, {
...
});
if
(
res
.
status
===
429
||
res
.
status
===
503
) {
const
retryAfter
=
Number
(
res
.
headers
.
get
(
'Retry-After'
));
if
(
Number
.
isFinite
(
retryAfter
)
&&
retryAfter
>
0
) {
await
new
Promise
((
r
)
=>
setTimeout
(
r
,
retryAfter
*
1000
));
// retry the request
}
}
​
Moderation errors
If your input was flagged, the
error.metadata
will contain information about the issue. The shape of the metadata is as follows:
type
ModerationErrorMetadata
=
{
reasons
:
string
[];
// Why your input was flagged
flagged_input
:
string
;
// The text segment that was flagged, limited to 100 characters. If the flagged input is longer than 100 characters, it will be truncated in the middle and replaced with ...
provider_name
:
string
;
// The name of the provider that requested moderation
model_slug
:
string
;
};
​
Guardrail errors
On inference endpoints (
/chat/completions
,
/responses
,
/messages
), a request can be blocked before it reaches a provider — for example by a content filter or prompt-injection detector configured via
guardrails
. When this happens, the response is a
403
with a message describing the block reason. The OpenRouter-shaped body below applies to
/chat/completions
and
/responses
; on
/messages
the block is returned as an Anthropic
permission_error
envelope (see
Anthropic Messages
below), with
openrouter_metadata
still carried at the top level when opted in:
{
"error"
: {
"code"
:
403
,
"message"
:
"Request blocked: prompt injection patterns detected"
,
"metadata"
: {
"patterns"
: [
"ignore all previous instructions"
]
}
}
}
When you opt in to
router metadata
via the
X-OpenRouter-Experimental-Metadata: enabled
header, the 403 response also includes the full
openrouter_metadata
object with routing context and a
pipeline
array detailing the guardrail stages that ran:
{
"error"
: {
"code"
:
403
,
"message"
:
"Request blocked: prompt injection patterns detected"
,
"metadata"
: {
"patterns"
: [
"ignore all previous instructions"
]
}
},
"openrouter_metadata"
: {
"requested"
:
"openai/gpt-4o"
,
"strategy"
:
"direct"
,
"region"
:
"iad"
,
"summary"
:
"available=1"
,
"attempt"
:
1
,
"is_byok"
:
false
,
"endpoints"
: {
"total"
:
1
,
"available"
: [
{
"provider"
:
"OpenAI"
,
"model"
:
"openai/gpt-4o"
,
"selected"
:
false
}
]
},
"pipeline"
: [
{
"type"
:
"guardrail"
,
"name"
:
"regex_pi_detection"
,
"guardrail_id"
:
"grd_abc123"
,
"guardrail_scope"
:
"api-key"
,
"summary"
:
"Blocked: prompt injection detected (1 pattern matched)"
,
"data"
: {
"action"
:
"blocked"
,
"detected"
:
true
,
"engines"
: [
"regex"
],
"patterns"
: [
"ignore all previous instructions"
]
}
}
]
}
}
See all 38 lines
The
openrouter_metadata
object follows the same shape as on successful responses — see
Pipeline Stages
for the full stage type and field reference.
​
Provider errors
OpenRouter normalizes every upstream provider error into the stable, typed
error_type
vocabulary documented under
Typed Error Codes
. The same
error_type
values describe what went wrong whether the provider error arrives in a non-streaming response body or as a mid-stream SSE event. Native protocol codes (the Anthropic
error.type
, the Responses
error.code
) are best-effort and can differ between formats —
error_type
is the field to rely on across all of them.
For Chat Completions, a provider error that interrupts generation carries
error_type
inside
error.metadata
:
{
"error"
: {
"code"
:
429
,
"message"
:
"Rate limit exceeded"
,
"metadata"
: {
"error_type"
:
"rate_limit_exceeded"
,
"provider_code"
:
"rate_limited"
}
}
}
The same value is carried on mid-stream errors and on the Anthropic and Responses skins — see
Skin-Specific Error Formats
for the exact wire location in each format.
​
Masking and raw provider details
When a request fails with a
500
, the
message
is replaced with a generic string and
provider_code
and
openrouter_metadata
are omitted, but
error_type
is still present (
server
).
For non-500 errors, the upstream provider’s own error code is surfaced in
error.metadata.provider_code
when available. Opt-in routing context (which provider was selected, fallback attempts, etc.) is carried in the
openrouter_metadata
object when the request sets
X-OpenRouter-Metadata
— it follows the same shape as on successful responses (routing-summary fields only; see
Pipeline Stages
).
​
When no content is generated
Occasionally, the model may not generate any content. This typically occurs when:
The model is warming up from a cold start
The system is scaling up to handle more requests
Warm-up times usually range from a few seconds to a few minutes, depending on the model and provider.
If you encounter persistent no-content issues, consider implementing a simple retry mechanism or trying again with a different provider or model that has more recent activity.
In some cases, you may still be charged for the prompt processing cost by the upstream provider, even if no content is generated.
​
Streaming error formats
When using streaming mode (
stream: true
), errors are handled differently depending on when they occur:
​
Pre-stream errors
Errors that occur before any tokens are sent follow the standard error format above, with appropriate HTTP status codes. At this stage the HTTP response hasn’t been committed yet, so OpenRouter can:
Return a proper HTTP error status (4xx/5xx)
Silently retry with a different provider endpoint if
fallback routing
is enabled
Apply rate-limit or auth checks before any work begins
You’ll see pre-stream errors for issues like invalid API keys, malformed requests, or when every available provider endpoint is exhausted before streaming starts.
​
Mid-stream errors
Once the first token has been written to the client, the HTTP
200 OK
status and headers are already committed — they can’t be changed. If the provider fails at this point, OpenRouter
cannot
silently fail over to another provider because partial content has already been delivered to your application. The error must arrive in-band as an SSE event.
Common causes of mid-stream errors:
Provider disconnect
— the upstream connection drops after partial output (network issue, provider crash, load balancer timeout)
Provider timeout
— the model stops responding mid-generation and the read deadline expires
Token limit hit during generation
— the model reaches
max_tokens
or the context window fills up while producing output
Output content filter
— a content moderation system flags generated text after some of it was already streamed
Provider overload
— the upstream returns a rate-limit or capacity error after beginning to stream
If an error occurs before any tokens are written — even on a streaming request — OpenRouter can still retry with a backup provider transparently. Mid-stream errors only happen when partial content has already been committed to your stream, making failover impossible.
Mid-stream errors are sent as Server-Sent Events (SSE) with a unified structure that includes both the error details and a completion choice:
type
MidStreamError
=
{
id
:
string
;
object
:
'chat.completion.chunk'
;
created
:
number
;
model
:
string
;
provider
:
string
;
error
:
{
code
:
number
;
// HTTP status code (e.g. 400, 429, 502)
message
:
string
;
metadata
?:
{
error_type
:
string
;
// Typed error code — see table below
provider_code
?:
string
;
// Original upstream error code (omitted on 500s)
};
};
choices
:
[{
index
:
0
;
delta
:
{
content
:
''
};
finish_reason
:
'error'
;
native_finish_reason
?:
string
;
}];
};
See all 21 lines
Example SSE data:
data: {"id":"gen-abc123","object":"chat.completion.chunk","created":1234567890,"model":"openai/gpt-4o","provider":"OpenAI","error":{"code":429,"message":"Rate limit exceeded","metadata":{"error_type":"rate_limit_exceeded"}},"choices":[{"index":0,"delta":{"content":""},"finish_reason":"error"}]}
Key characteristics:
The error appears at the
top level
alongside standard response fields
error.metadata.error_type
carries a typed code you can switch on programmatically — see
Typed Error Codes
for the full list
A
choices
array is included with
finish_reason: "error"
to properly terminate the stream
The HTTP status remains 200 OK since headers were already sent
The stream is terminated after this event
On 500-class errors,
error.message
is replaced with a generic string and
provider_code
is omitted to prevent leaking upstream details
​
Typed error codes
When a provider error reaches your application, OpenRouter tags it with a canonical
error_type
string — both on the non-streaming response body and on mid-stream SSE events. Use this value, not the HTTP status code alone, to programmatically distinguish error categories. It is stable across all three API skins even when the native protocol code is lossy.
Where
error_type
appears depends on the skin and path:
Chat Completions
:
error.metadata.error_type
— on the mid-stream error chunk (see
Mid-Stream Errors
) and on the non-streaming response when a provider error interrupts generation.
Anthropic Messages
:
error.error_type
on the SSE
error
event and the non-streaming error envelope.
Responses
: top-level
error_type
on the failed response, for both the streaming
response.failed
event and the non-streaming JSON body.
The HTTP status each
error_type
maps to is listed in the tables below.
​
Token and length limits
error_type
HTTP Status
Description
context_length_exceeded
The combined input and output tokens exceed the model’s context window.
max_tokens_exceeded
Generation stopped because
max_tokens
(or
max_completion_tokens
) was reached.
token_limit_exceeded
A token budget enforced by OpenRouter (e.g. credit-based cap) was exceeded.
string_too_long
A single string field in the request (system prompt, user message, etc.) exceeded the provider’s per-field character limit.
​
Authentication and authorization
error_type
HTTP Status
Description
authentication
The API key is missing, invalid, or revoked.
permission_denied
The key is valid but lacks the required permission or the request was blocked by a
guardrail
.
payment_required
The account or API key has insufficient credits.
Add credits
and retry.
​
Rate limiting and availability
error_type
HTTP Status
Description
rate_limit_exceeded
Request- or token-level rate limit hit. Respect the
Retry-After
header before retrying.
provider_overloaded
The upstream provider is temporarily overloaded. Retry after a short delay.
provider_unavailable
The upstream provider returned an invalid or empty response. OpenRouter may auto-retry with another provider if fallback routing is enabled.
​
Request validation
error_type
HTTP Status
Description
invalid_request
A request parameter is malformed or missing.
invalid_prompt
A specific message in the
messages
array is invalid (e.g. unsupported role, empty content).
not_found
The requested resource (model, file, etc.) does not exist.
precondition_failed
A precondition header (e.g.
If-Match
) was not satisfied.
payload_too_large
The request body exceeds the maximum allowed size.
unprocessable
The request is syntactically valid but semantically unprocessable.
​
Content policy
error_type
HTTP Status
Description
content_policy_violation
The input or output was flagged by a content filter (provider- or OpenRouter-level).
refusal
The model explicitly refused to comply with the request (e.g. safety refusal).
​
Image errors
error_type
HTTP Status
Description
invalid_image
An image in the request is corrupt or unreadable.
image_too_large
An image exceeds the provider’s maximum file size or pixel dimensions.
image_too_small
An image is below the provider’s minimum pixel dimensions.
unsupported_image_format
The image format is not supported by the provider.
image_not_found
The referenced image URL or file ID could not be resolved.
image_download_failed
OpenRouter could not download the image from the provided URL (DNS failure, timeout, non-200 response, etc.).
​
Generic
error_type
HTTP Status
Description
server
An unexpected internal error. The upstream error message is masked on this type.
timeout
The provider did not respond within the allowed time.
unmapped
An upstream error that doesn’t map to any known category.
error.metadata.provider_code
may contain the original code.
​
Model availability errors
When a requested model cannot be served — it doesn’t exist, is deprecated, has no routable endpoints under your preferences, or all of its providers are at capacity — the error response carries an additional
error.availability
object. It is
additive
:
error_type
,
http_status
, and
message
keep their existing values, so existing error handling keeps working. Use
availability.code
— not the HTTP status or the message prose — to programmatically distinguish availability conditions.
The same
availability
object is emitted on streaming error chunks for all three API skins (Chat Completions, Responses, Anthropic Messages).
​
Availability codes
Every model-availability failure maps to exactly one code. Codes are stable forever; new codes may be added in the future, so treat an unrecognized code defensively and fall back to
retryable
.
availability.code
HTTP
error_type
Retryable
Meaning
Recommended client action
model_not_found
invalid_request
No
The model id is unknown or invalid.
Do not retry. Check the slug for typos or a legacy alias; use
fallback_models
suggestions when present.
wrong_endpoint
invalid_request
No
The model exists but was called on the wrong endpoint (e.g. an embedding model on
/chat/completions
).
Retry on the endpoint named in
constraint.detail
.
no_endpoints
not_found
No
The model exists but has zero routable endpoints (none configured, all disabled/hidden, or all removed by your provider preferences).
Do not retry the identical request. Loosen provider routing preferences or choose another model.
model_deprecated
not_found
No
The model was deprecated or removed (sunset models, ended stealth alphas).
Migrate to the successor in
fallback_models
; see
docs_url
for the deprecation notice.
model_unavailable_upstream
not_found
No
Every endpoint for the model returns 404 upstream — the provider removed it.
Do not retry; pick another model (
fallback_models
when present).
capacity_exhausted
rate_limit_exceeded
Yes
All providers for the model are at capacity (all attempts returned 429).
Retry after
retry_after
seconds (also sent as the
Retry-After
header), or route to a
fallback_models
entry.
temporarily_unavailable
provider_overloaded
Yes
All attempts failed with transient upstream 5xx responses or timeouts.
Retry with backoff, honoring
retry_after
when present; consider
fallback_models
for latency-sensitive paths.
region_restricted
permission_denied
No
The model is geo-gated and not available in your region.
Do not retry from the same region. Adjust provider routing or serve traffic from an allowed region.
privacy_restricted
not_found
No
Your data-policy preferences (ZDR, training opt-out, free-model publication) eliminated every endpoint.
Relax the policy named in
excluded_by
(e.g. at
privacy settings
) or choose another model.
constraint_filtered
not_found
No
A routing constraint — max price, context length,
require_parameters
, quantization, data region — eliminated every endpoint.
Relax the constraint named in
constraint.field
/
excluded_by
and retry.
free_variant_ended
not_found
No
The
:free
variant’s promotion ended; only paid endpoints remain.
Switch to the paid slug in
fallback_models
(normal pricing applies) or pick another model.
no_endpoints
and
model_unavailable_upstream
are non-retryable today, but the router may mark individual occurrences
retryable: true
when the underlying condition is transient. Always switch on
availability.retryable
, not just the code.
​
The
availability
object
type
AvailabilityError
=
{
code
:
string
;
// one of the codes above — the machine-readable discriminator
retryable
:
boolean
;
// would an identical retry plausibly succeed?
retry_after
?:
number
|
null
;
// seconds to wait before retrying, when known (429/503 paths)
requested_models
:
string
[];
// the raw model slugs you sent
affected_providers
?:
string
[]
|
null
;
// providers whose endpoints were attempted or excluded
excluded_by
?:
string
[];
// routing filter(s) that removed the last endpoints
fallback_models
?:
string
[];
// suggested alternative model slugs; may be empty or omitted
constraint
?:
{
// present on constraint_filtered and wrong_endpoint
field
:
string
;
// e.g. "max_price"
detail
:
string
;
// human-readable explanation
};
docs_url
:
string
;
// anchor for this code on this page
};
code
,
retryable
,
requested_models
, and
docs_url
are always present on an availability error. Other fields are omitted (never serialized as empty strings) when not applicable.
excluded_by
values are drawn from a fixed vocabulary:
geo
,
data_region
,
data_policy:zdr
,
data_policy:training
,
max_price
,
context_length
,
require_parameters
,
quantization
,
allowed_providers
.
For debugging,
error.metadata.previous_errors
carries the per-provider attempt history in a slim, stable shape —
{ "provider": "...", "code": "...", "status": 429 }
— and never includes raw upstream provider error bodies.
​
Retrying with
retry_after
For
capacity_exhausted
and
temporarily_unavailable
,
availability.retry_after
mirrors the
Retry-After
response header in the body and is populated whenever any attempted endpoint supplied a retry hint (the minimum across attempts). Both are expressed in seconds:
const
res
=
await
fetch
(
'https://openrouter.ai/api/v1/chat/completions'
, {
...
});
const
body
=
await
res
.
json
();
const
availability
=
body
.
error
?.
availability
;
if
(
availability
?.
retryable
) {
const
waitSeconds
=
availability
.
retry_after
??
Number
(
res
.
headers
.
get
(
'Retry-After'
))
||
2
**
attempt
;
// exponential backoff fallback when no hint is given
await
new
Promise
((
r
)
=>
setTimeout
(
r
,
waitSeconds
*
1000
));
// retry the identical request
}
The OpenAI, Anthropic, Vercel AI, and OpenRouter SDKs already honor the
Retry-After
header;
availability.retry_after
exposes the same signal to clients that inspect bodies rather than headers.
​
Using
fallback_models
suggestions
fallback_models
contains suggested alternative slugs the router can point to cheaply — the paid variant after a
:free
promotion ends, the successor of a deprecated model, or same-family alternatives. It is omitted when no suggestion is available and never delays the error response.
Suggestions are hints, not guarantees: a fallback may differ in price, context window, or capabilities, and it may be unavailable under your own routing preferences. Validate the slug against your requirements (or surface it to your user) before silently switching models.
​
Example availability error payloads
All providers at capacity —
capacity_exhausted
(retryable):
{
"error"
: {
"message"
:
"All providers for anthropic/claude-sonnet-4.5 are at capacity. Retry after 40 seconds."
,
"error_type"
:
"rate_limit_exceeded"
,
"http_status"
:
429
,
"availability"
: {
"code"
:
"capacity_exhausted"
,
"retryable"
:
true
,
"retry_after"
:
40
,
"requested_models"
: [
"anthropic/claude-sonnet-4.5"
],
"affected_providers"
: [
"anthropic"
,
"amazon-bedrock"
,
"google-vertex"
],
"fallback_models"
: [
"anthropic/claude-haiku-4.5"
],
"docs_url"
:
"https://openrouter.ai/docs/errors#capacity_exhausted"
},
"metadata"
: {
"previous_errors"
: [
{
"provider"
:
"anthropic"
,
"code"
:
"capacity_exhausted"
,
"status"
:
429
},
{
"provider"
:
"amazon-bedrock"
,
"code"
:
"capacity_exhausted"
,
"status"
:
429
}
]
}
}
}
Deprecated stealth model —
model_deprecated
:
{
"error"
: {
"message"
:
"Quasar Alpha was a stealth model, revealed on April 14th as an early testing version of GPT-4.1."
,
"error_type"
:
"not_found"
,
"http_status"
:
404
,
"availability"
: {
"code"
:
"model_deprecated"
,
"retryable"
:
false
,
"requested_models"
: [
"openrouter/quasar-alpha"
],
"fallback_models"
: [
"openai/gpt-4.1"
],
"docs_url"
:
"https://openrouter.ai/docs/errors#model_deprecated"
}
}
}
Privacy restriction eliminated all endpoints —
privacy_restricted
:
{
"error"
: {
"message"
:
"No endpoints for openai/gpt-5.2 match your data policy (zero data retention). Adjust at https://openrouter.ai/settings/privacy."
,
"error_type"
:
"not_found"
,
"http_status"
:
404
,
"availability"
: {
"code"
:
"privacy_restricted"
,
"retryable"
:
false
,
"requested_models"
: [
"openai/gpt-5.2"
],
"affected_providers"
: [
"openai"
,
"azure"
],
"excluded_by"
: [
"data_policy:zdr"
],
"docs_url"
:
"https://openrouter.ai/docs/errors#privacy_restricted"
}
}
}
Unknown model id —
model_not_found
:
{
"error"
: {
"message"
:
"acme/fake-1.0 is not a valid model ID"
,
"error_type"
:
"invalid_request"
,
"http_status"
:
400
,
"availability"
: {
"code"
:
"model_not_found"
,
"retryable"
:
false
,
"requested_models"
: [
"acme/fake-1.0"
],
"docs_url"
:
"https://openrouter.ai/docs/errors#model_not_found"
}
}
}
​
Skin-specific error formats
OpenRouter exposes three API skins. Each translates the same internal provider error types into its own wire format, for non-streaming responses and in-stream errors alike. In every case
error_type
is the stable field; the wire location differs per skin.
​
Chat Completions (
/api/v1/chat/completions
)
Mid-stream errors appear as a
chat.completion.chunk
with a top-level
error
object (shape shown
above
). The
error.metadata.error_type
field carries the typed code.
For non-streaming requests where a provider error occurs, the error is embedded in the final response alongside any partial content:
{
"choices"
: [{
"message"
: {
"role"
:
"assistant"
,
"content"
:
"partial output..."
},
"finish_reason"
:
"error"
,
"error"
: {
"code"
:
502
,
"message"
:
"Provider disconnected mid-stream"
,
"metadata"
: {
"error_type"
:
"provider_unavailable"
}
}
}]
}
​
Responses API (
/api/v1/responses
)
The Responses API maps internal error types to the OpenAI Responses error code set. The mapping is narrower — many distinct internal types collapse to
server_error
— so the precise reason is preserved in a top-level
error_type
field on the response, outside the native
error
object:
Internal
error_type
Responses API
code
rate_limit_exceeded
rate_limit_exceeded
context_length_exceeded
,
invalid_request
invalid_prompt
content_policy_violation
image_content_policy_violation
authentication
,
provider_overloaded
,
provider_unavailable
,
timeout
,
server
server_error
All others (including
invalid_prompt
)
server_error
Both the streaming terminal event and the non-streaming JSON body carry the canonical
error_type
at the top level of the response object. For example, an authentication failure collapses to the native
server_error
code but keeps
error_type: "authentication"
:
{
"id"
:
"resp_abc123"
,
"status"
:
"failed"
,
"error"
: {
"code"
:
"server_error"
,
"message"
:
"Invalid credentials"
},
"error_type"
:
"authentication"
}
Streaming errors surface as one of three SSE event types, each wrapping the same response object:
response.failed
— terminal event when the response could not complete:
{
"type"
:
"response.failed"
,
"response"
: {
"id"
:
"resp_abc123"
,
"status"
:
"failed"
,
"error"
: {
"code"
:
"server_error"
,
"message"
:
"Internal server error"
},
"error_type"
:
"server"
}
}
response.error
— error during response generation:
{
"type"
:
"response.error"
,
"error"
: {
"code"
:
"rate_limit_exceeded"
,
"message"
:
"Rate limit exceeded"
}
}
error
— plain error event (matches upstream OpenAI behavior):
{
"type"
:
"error"
,
"error"
: {
"code"
:
"invalid_api_key"
,
"message"
:
"Invalid API key provided"
}
}
​
Error code transformations
Certain token/length errors are transformed into successful completions instead of failures:
error_type
Transformed To
Finish Reason
context_length_exceeded
Success
length
max_tokens_exceeded
Success
length
token_limit_exceeded
Success
length
string_too_long
Success
length
This allows graceful handling of limit-based errors without treating them as failures.
​
Anthropic Messages (
/api/v1/messages
)
The Anthropic Messages skin maps internal types to Anthropic-native error type strings:
Internal
error_type
Anthropic
error.type
authentication
authentication_error
permission_denied
permission_error
payment_required
billing_error
not_found
,
image_not_found
not_found_error
rate_limit_exceeded
rate_limit_error
provider_overloaded
overloaded_error
timeout
timeout_error
context_length_exceeded
,
content_policy_violation
,
invalid_request
, …
invalid_request_error
provider_unavailable
,
server
,
unmapped
, …
api_error
Because the native
error.type
is lossy (many internal types collapse to
api_error
), the canonical
error_type
is added inside the
error
object alongside it. This holds for both the non-streaming error envelope and mid-stream SSE
error
events.
Non-streaming error envelope (pre-Router errors such as authentication failures carry a
null
request_id
; post-Router errors carry the
gen-
generation ID):
{
"type"
:
"error"
,
"error"
: {
"type"
:
"authentication_error"
,
"message"
:
"Invalid credentials"
,
"error_type"
:
"authentication"
},
"request_id"
:
null
}
Mid-stream errors are emitted as an SSE
error
event with the same shape:
{
"type"
:
"error"
,
"error"
: {
"type"
:
"overloaded_error"
,
"message"
:
"Provider is temporarily overloaded"
,
"error_type"
:
"provider_overloaded"
}
}
​
Debugging
OpenRouter provides a
debug
option that allows you to inspect the exact request body that was sent to the upstream provider. This works with both the Chat Completions API (
/api/v1/chat/completions
) and the Responses API (
/api/v1/responses
). Useful for understanding how OpenRouter transforms your request parameters for different providers.
​
Debug option shape
The debug option is an object with the following shape:
type
DebugOptions
=
{
echo_upstream_body
?:
boolean
;
// If true, returns the transformed request body sent to the provider
};
​
Usage
To enable debug output, include the
debug
parameter in your request:
​
Chat Completions
TypeScript
Python
fetch
(
'https://openrouter.ai/api/v1/chat/completions'
, {
method:
'POST'
,
headers:
{
Authorization:
'Bearer <OPENROUTER_API_KEY>'
,
'Content-Type'
:
'application/json'
,
},
body:
JSON
.
stringify
({
model:
'anthropic/claude-haiku-4.5'
,
stream:
true
,
// Debug only works with streaming
messages:
[
{
role:
'system'
,
content:
'You are a helpful assistant.'
},
{
role:
'user'
,
content:
'Hello!'
},
],
debug:
{
echo_upstream_body:
true
,
},
}),
});
const
text
=
await
response
.
text
();
for
(
const
line
of
text
.
split
(
'
\n
'
)) {
if
(
!
line
.
startsWith
(
'data: '
))
continue
;
const
data
=
line
.
slice
(
6
);
if
(
data
===
'[DONE]'
)
break
;
const
parsed
=
JSON
.
parse
(
data
);
if
(
parsed
.
debug
?.
echo_upstream_body
) {
console
.
log
(
'
\n
Debug:'
,
JSON
.
stringify
(
parsed
.
debug
.
echo_upstream_body
,
null
,
2
));
}
process
.
stdout
.
write
(
parsed
.
choices
?.[
0
]?.
delta
?.
content
??
''
);
}
See all 35 lines
import
requests
import
json
response
=
requests.post(
url
=
"https://openrouter.ai/api/v1/chat/completions"
,
headers
=
{
"Authorization"
:
"Bearer <OPENROUTER_API_KEY>"
,
"Content-Type"
:
"application/json"
,
},
data
=
json.dumps({
"model"
:
"anthropic/claude-haiku-4.5"
,
"stream"
:
True
,
"messages"
: [
{
"role"
:
"system"
,
"content"
:
"You are a helpful assistant."
},
{
"role"
:
"user"
,
"content"
:
"Hello!"
}
],
"debug"
: {
"echo_upstream_body"
:
True
}
}),
stream
=
True
)
for
line
in
response.iter_lines():
if
line:
text
=
line.decode(
'utf-8'
)
if
'echo_upstream_body'
in
text:
print
(text)
​
Responses API
TypeScript
Python
fetch
(
'https://openrouter.ai/api/v1/responses'
, {
method:
'POST'
,
headers:
{
Authorization:
'Bearer <OPENROUTER_API_KEY>'
,
'Content-Type'
:
'application/json'
,
},
body:
JSON
.
stringify
({
model:
'anthropic/claude-haiku-4.5'
,
stream:
true
,
input:
'Hello!'
,
debug:
{
echo_upstream_body:
true
,
},
}),
});
const
text
=
await
response
.
text
();
for
(
const
line
of
text
.
split
(
'
\n
'
)) {
if
(
!
line
.
startsWith
(
'data: '
))
continue
;
const
data
=
line
.
slice
(
6
);
if
(
data
===
'[DONE]'
)
break
;
const
parsed
=
JSON
.
parse
(
data
);
if
(
parsed
.
type
===
'response.debug'
) {
console
.
log
(
'
\n
Debug:'
,
JSON
.
stringify
(
parsed
.
debug
,
null
,
2
));
}
}
See all 30 lines
import
requests
import
json
response
=
requests.post(
url
=
"https://openrouter.ai/api/v1/responses"
,
headers
=
{
"Authorization"
:
"Bearer <OPENROUTER_API_KEY>"
,
"Content-Type"
:
"application/json"
,
},
data
=
json.dumps({
"model"
:
"anthropic/claude-haiku-4.5"
,
"stream"
:
True
,
"input"
:
"Hello!"
,
"debug"
: {
"echo_upstream_body"
:
True
}
}),
stream
=
True
)
for
line
in
response.iter_lines():
if
line:
text
=
line.decode(
'utf-8'
)
if
'response.debug'
in
text:
print
(text)
​
Debug response format
​
Chat Completions
When
debug.echo_upstream_body
is set to
true
, OpenRouter sends a debug chunk as the
first chunk
in the streaming response. This chunk has an empty
choices
array and includes a
debug
field with the transformed request body:
{
"id"
:
"gen-xxxxx"
,
"provider"
:
"Anthropic"
,
"model"
:
"anthropic/claude-haiku-4.5"
,
"object"
:
"chat.completion.chunk"
,
"created"
:
1234567890
,
"choices"
: [],
"debug"
: {
"echo_upstream_body"
: {
"system"
: [
{
"type"
:
"text"
,
"text"
:
"You are a helpful assistant."
}
],
"messages"
: [
{
"role"
:
"user"
,
"content"
:
"Hello!"
}
],
"model"
:
"claude-haiku-4-5-20251001"
,
"stream"
:
true
,
"max_tokens"
:
64000
,
"temperature"
:
1
}
}
}
See all 22 lines
​
Responses API
On the Responses API, debug data arrives as a
response.debug
SSE event:
{
"type"
:
"response.debug"
,
"debug"
: {
"echo_upstream_body"
: {
"model"
:
"claude-haiku-4-5-20251001"
,
"messages"
: [
{
"role"
:
"user"
,
"content"
:
"Hello!"
}
],
"stream"
:
true
,
"max_tokens"
:
64000
,
"temperature"
:
1
}
},
"sequence_number"
:
0
}
​
Important notes
Streaming Only
The debug option
only works with streaming mode
(
stream: true
). Non-streaming requests will ignore the debug parameter.
Not for Production
The debug flag should
not be used in production environments
. It is intended for development and debugging purposes only, as it may potentially return sensitive information included in the request that was not intended to be visible elsewhere.
​
Use cases
The debug output is particularly useful for:
Understanding Parameter Transformations
: See how OpenRouter maps your parameters to provider-specific formats (e.g., how
max_tokens
is set, how
temperature
is handled).
Verifying Message Formatting
: Check how OpenRouter combines and formats your messages for different providers (e.g., how system messages are concatenated, how user messages are merged).
Checking Applied Defaults
: See what default values OpenRouter applies when parameters are not specified in your request.
Debugging Provider Fallbacks
: When using provider fallbacks, a debug chunk will be sent for
each attempted provider
, allowing you to see which providers were tried and what parameters were sent to each.
​
Privacy and redaction
OpenRouter will make a best effort to automatically redact potentially sensitive or noisy data from debug output. Remember that the debug option is not intended for production.
Parameters
Overview
⌘
I
Assistant
Responses are generated using AI and may contain mistakes.
