Errors and Debugging

API Errors and Debugging

For errors, OpenRouter returns a JSON response with the following shape:

1type ErrorResponse = {
2 error: {
3 code: number;
4 message: string;
5 metadata?: Record<string, unknown>;
6 };
7};

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 200 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:

1const request = await fetch('https://openrouter.ai/...');
2console.log(request.status); // Will be an error code unless the model started processing your request
3const response = await request.json();
4console.error(response.error?.status); // Will be an error code
5console.error(response.error?.message);

Error Codes

  • 400: Bad Request (invalid or missing params, CORS)
  • 401: Invalid credentials (OAuth session expired, disabled/invalid API key)
  • 402: Your account or API key has insufficient credits. Add more credits and retry the request.
  • 403: Forbidden (insufficient permissions, guardrail block, or moderation flag)
  • 408: Your request timed out
  • 429: You are being rate limited
  • 502: Your chosen model is down or we received an invalid response from it
  • 503: There is no available model provider that meets your routing requirements

Retry-After Header

On 429 and 503 responses, OpenRouter may include a standard HTTP Retry-After response header indicating how many seconds to wait before retrying.

1HTTP/1.1 429 Too Many Requests
2Retry-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:

1const res = await fetch('https://openrouter.ai/api/v1/chat/completions', { ... });
2if (res.status === 429 || res.status === 503) {
3 const retryAfter = Number(res.headers.get('Retry-After'));
4 if (Number.isFinite(retryAfter) && retryAfter > 0) {
5 await new Promise((r) => setTimeout(r, retryAfter * 1000));
6 // retry the request
7 }
8}

Moderation Errors

If your input was flagged, the error.metadata will contain information about the issue. The shape of the metadata is as follows:

1type ModerationErrorMetadata = {
2 reasons: string[]; // Why your input was flagged
3 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 ...
4 provider_name: string; // The name of the provider that requested moderation
5 model_slug: string;
6};

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:

1{
2 "error": {
3 "code": 403,
4 "message": "Request blocked: prompt injection patterns detected",
5 "metadata": {
6 "patterns": ["ignore all previous instructions"]
7 }
8 }
9}

When you opt in to router metadata via the X-OpenRouter-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:

1{
2 "error": {
3 "code": 403,
4 "message": "Request blocked: prompt injection patterns detected",
5 "metadata": {
6 "patterns": ["ignore all previous instructions"]
7 }
8 },
9 "openrouter_metadata": {
10 "requested": "openai/gpt-4o",
11 "strategy": "direct",
12 "region": "iad",
13 "summary": "available=1",
14 "attempt": 1,
15 "is_byok": false,
16 "endpoints": {
17 "total": 1,
18 "available": [
19 { "provider": "OpenAI", "model": "openai/gpt-4o", "selected": false }
20 ]
21 },
22 "pipeline": [
23 {
24 "type": "guardrail",
25 "name": "regex_pi_detection",
26 "guardrail_id": "grd_abc123",
27 "guardrail_scope": "api-key",
28 "summary": "Blocked: prompt injection detected (1 pattern matched)",
29 "data": {
30 "action": "blocked",
31 "detected": true,
32 "engines": ["regex"],
33 "patterns": ["ignore all previous instructions"]
34 }
35 }
36 ]
37 }
38}

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

If the model provider encounters an error, the error.metadata will contain information about the issue. The shape of the metadata is as follows:

1type ProviderErrorMetadata = {
2 provider_name: string; // The name of the provider that encountered the error
3 raw: unknown; // The raw error from the provider
4};

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.

Additionally, be aware that 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:

1type MidStreamError = {
2 id: string;
3 object: 'chat.completion.chunk';
4 created: number;
5 model: string;
6 provider: string;
7 error: {
8 code: number; // HTTP status code (e.g. 400, 429, 502)
9 message: string;
10 metadata?: {
11 error_type: string; // Typed error code — see table below
12 provider_code?: string; // Original upstream error code (omitted on 500s)
13 };
14 };
15 choices: [{
16 index: 0;
17 delta: { content: '' };
18 finish_reason: 'error';
19 native_finish_reason?: string;
20 }];
21};

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

Every mid-stream error carries an error_type string in its metadata. Use this value — not the HTTP status code alone — to programmatically distinguish error categories.

Token and Length Limits

error_typeHTTP StatusDescription
context_length_exceeded400The combined input and output tokens exceed the model’s context window.
max_tokens_exceeded400Generation stopped because max_tokens (or max_completion_tokens) was reached.
token_limit_exceeded400A token budget enforced by OpenRouter (e.g. credit-based cap) was exceeded.
string_too_long400A single string field in the request (system prompt, user message, etc.) exceeded the provider’s per-field character limit.

Authentication and Authorization

error_typeHTTP StatusDescription
authentication401The API key is missing, invalid, or revoked.
permission_denied403The key is valid but lacks the required permission or the request was blocked by a guardrail.
payment_required402The account or API key has insufficient credits. Add credits and retry.

Rate Limiting and Availability

error_typeHTTP StatusDescription
rate_limit_exceeded429Request- or token-level rate limit hit. Respect the Retry-After header before retrying.
provider_overloaded503The upstream provider is temporarily overloaded. Retry after a short delay.
provider_unavailable502The upstream provider returned an invalid or empty response. OpenRouter may auto-retry with another provider if fallback routing is enabled.

Request Validation

error_typeHTTP StatusDescription
invalid_request400A request parameter is malformed or missing.
invalid_prompt400A specific message in the messages array is invalid (e.g. unsupported role, empty content).
not_found404The requested resource (model, file, etc.) does not exist.
precondition_failed412A precondition header (e.g. If-Match) was not satisfied.
payload_too_large413The request body exceeds the maximum allowed size.
unprocessable422The request is syntactically valid but semantically unprocessable.

Content Policy

error_typeHTTP StatusDescription
content_policy_violation400The input or output was flagged by a content filter (provider- or OpenRouter-level).
refusal400The model explicitly refused to comply with the request (e.g. safety refusal).

Image Errors

error_typeHTTP StatusDescription
invalid_image400An image in the request is corrupt or unreadable.
image_too_large400An image exceeds the provider’s maximum file size or pixel dimensions.
image_too_small400An image is below the provider’s minimum pixel dimensions.
unsupported_image_format400The image format is not supported by the provider.
image_not_found404The referenced image URL or file ID could not be resolved.
image_download_failed400OpenRouter could not download the image from the provided URL (DNS failure, timeout, non-200 response, etc.).

Generic

error_typeHTTP StatusDescription
server500An unexpected internal error. The upstream error message is masked on this type.
timeout504The provider did not respond within the allowed time.
unmapped500An upstream error that doesn’t map to any known category. error.metadata.provider_code may contain the original code.

Skin-Specific Error Formats

OpenRouter exposes three API skins. Each translates the same internal error types into its own wire format.

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 from the table.

For non-streaming requests where some content was already generated, the error is embedded in the final response alongside the partial content:

1{
2 "choices": [{
3 "message": { "role": "assistant", "content": "partial output..." },
4 "finish_reason": "error",
5 "error": {
6 "code": 502,
7 "message": "Provider disconnected mid-stream",
8 "metadata": { "error_type": "provider_unavailable" }
9 }
10 }]
11}

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:

Internal error_typeResponses API code
rate_limit_exceededrate_limit_exceeded
context_length_exceeded, invalid_requestinvalid_prompt
content_policy_violationimage_content_policy_violation
authentication, provider_overloaded, provider_unavailable, timeout, serverserver_error
All others (including invalid_prompt)server_error

Errors surface as one of three SSE event types:

  1. response.failed — terminal event when the response could not complete:

    1{
    2 "type": "response.failed",
    3 "response": {
    4 "id": "resp_abc123",
    5 "status": "failed",
    6 "error": { "code": "server_error", "message": "Internal server error" }
    7 }
    8}
  2. response.error — error during response generation:

    1{
    2 "type": "response.error",
    3 "error": { "code": "rate_limit_exceeded", "message": "Rate limit exceeded" }
    4}
  3. error — plain error event (matches upstream OpenAI behavior):

    1{
    2 "type": "error",
    3 "error": { "code": "invalid_api_key", "message": "Invalid API key provided" }
    4}

Error Code Transformations

Certain token/length errors are transformed into successful completions instead of failures:

error_typeTransformed ToFinish Reason
context_length_exceededSuccesslength
max_tokens_exceededSuccesslength
token_limit_exceededSuccesslength
string_too_longSuccesslength

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_typeAnthropic error.type
authenticationauthentication_error
rate_limit_exceededrate_limit_error
context_length_exceeded, content_policy_violation, invalid_requestinvalid_request_error
provider_overloadedoverloaded_error
provider_unavailable, timeout, serverapi_error
All othersapi_error

Mid-stream errors are emitted as an SSE error event:

1{
2 "type": "error",
3 "error": { "type": "overloaded_error", "message": "Provider is temporarily overloaded" }
4}

Debugging

OpenRouter provides a debug option that allows you to inspect the exact request body that was sent to the upstream provider. This is useful for understanding how OpenRouter transforms your request parameters to work with different providers.

Debug Option Shape

The debug option is an object with the following shape:

1type DebugOptions = {
2 echo_upstream_body?: boolean; // If true, returns the transformed request body sent to the provider
3};

Usage

To enable debug output, include the debug parameter in your request:

1fetch('https://openrouter.ai/api/v1/chat/completions', {
2 method: 'POST',
3 headers: {
4 Authorization: 'Bearer <OPENROUTER_API_KEY>',
5 'Content-Type': 'application/json',
6 },
7 body: JSON.stringify({
8 model: 'anthropic/claude-haiku-4.5',
9 stream: true, // Debug only works with streaming
10 messages: [
11 { role: 'system', content: 'You are a helpful assistant.' },
12 { role: 'user', content: 'Hello!' },
13 ],
14 debug: {
15 echo_upstream_body: true,
16 },
17 }),
18});
19
20const text = await response.text();
21
22for (const line of text.split('\n')) {
23 if (!line.startsWith('data: ')) continue;
24
25 const data = line.slice(6);
26 if (data === '[DONE]') break;
27
28 const parsed = JSON.parse(data);
29
30 if (parsed.debug?.echo_upstream_body) {
31 console.log('\nDebug:', JSON.stringify(parsed.debug.echo_upstream_body, null, 2));
32 }
33
34 process.stdout.write(parsed.choices?.[0]?.delta?.content ?? '');
35}

Debug Response Format

When debug.echo_upstream_body is set to true, OpenRouter will send a debug chunk as the first chunk in the streaming response. This chunk will have an empty choices array and include a debug field containing the transformed request body:

1{
2 "id": "gen-xxxxx",
3 "provider": "Anthropic",
4 "model": "anthropic/claude-haiku-4.5",
5 "object": "chat.completion.chunk",
6 "created": 1234567890,
7 "choices": [],
8 "debug": {
9 "echo_upstream_body": {
10 "system": [
11 { "type": "text", "text": "You are a helpful assistant." }
12 ],
13 "messages": [
14 { "role": "user", "content": "Hello!" }
15 ],
16 "model": "claude-haiku-4-5-20251001",
17 "stream": true,
18 "max_tokens": 64000,
19 "temperature": 1
20 }
21 }
22}

Important Notes

Streaming Chat Completions Only

The debug option only works with streaming mode (stream: true) for the Chat Completions API. Non-streaming requests and Responses API 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:

  1. Understanding Parameter Transformations: See how OpenRouter maps your parameters to provider-specific formats (e.g., how max_tokens is set, how temperature is handled).

  2. 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).

  3. Checking Applied Defaults: See what default values OpenRouter applies when parameters are not specified in your request.

  4. 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.