OpenRouter Common Errors
Overview
OpenRouter returns standard HTTP error codes plus OpenRouter-specific error codes in the response body. The most common: 401 (auth), 402 (credits), 429 (rate limit), 400 (bad request), and 5xx (upstream provider errors). Each error includes a code field and a human-readable message. This skill covers every common error, its root cause, and the exact fix.
Prerequisites
- An OpenRouter API key (
sk-or-v1-...) exported as OPENROUTER_API_KEY — see the openrouter-install-auth skill for setup
curl and jq to run the Diagnostic Script
- Python 3.8+ with the OpenAI SDK for the categorized error handler; Node.js 18+ for the TypeScript typed-error classifier in the references
- The
requests package if you use the Prevention Middleware's pre-flight model check
Instructions
- Identify the failure by HTTP status and
code using the Complete Error Reference table (400/401/402/403/408/429/5xx each map to a specific fix).
- Inspect the body per Error Response Format —
error.code, error.message, and error.metadata.provider_name tell you whether OpenRouter or the upstream provider failed.
- Run the Diagnostic Script: it checks auth via
GET /api/v1/auth/key, computes remaining credits, verifies the model exists in /api/v1/models, and fires a minimal 1-token completion.
- Wrap production calls with
safe_completion() from Python Error Handler — max_retries=3 auto-retries 429 and 5xx, and each exception class raises with its exact remedy.
- Add
validate_before_send() from Prevention Middleware to catch bad model IDs (with suggestions), malformed messages, and context overflows before spending money on a 400.
- If errors persist across retries and providers, check status.openrouter.ai per the Error Handling table.
Complete Error Reference
| HTTP |
Error Code |
Cause |
Fix |
| 400 |
bad_request |
Malformed request body |
Validate messages array format; ensure model ID includes provider prefix |
| 400 |
invalid_model |
Model ID not found |
Check model exists: curl -s https://openrouter.ai/api/v1/models | jq '.data[].id' |
| 400 |
context_length_exceeded |
Prompt + max_tokens > model limit |
Reduce prompt size or use a larger-context model |
| 400 |
invalid_tool_schema |
Tool definition has unsupported types |
Use basic JSON Schema types only (string, number, boolean, object, array) |
| 401 |
invalid_api_key |
Key malformed, revoked, or wrong |
Regenerate at openrouter.ai/keys; key must start with sk-or-v1- |
| 401 |
missing_api_key |
No Authorization header |
Add Authorization: Bearer sk-or-v1-... header |
| 402 |
insufficient_credits |
Credit balance is zero |
Top up at openrouter.ai/credits |
| 402 |
credit_limit_reached |
Per-key credit limit hit |
Increase key limit in dashboard or create new key |
| 403 |
key_disabled |
Key was disabled by admin |
Re-enable in dashboard or create new key |
| 408 |
request_timeout |
Model took too long |
Reduce max_tokens; use streaming; try faster model |
| 429 |
rate_limit_exceeded |
Too many requests per interval |
SDK auto-retries; increase max_retries; use multiple keys |
| 502 |
provider_error |
Upstream provider returned error |
Retry with backoff; try different provider via provider.order |
| 503 |
model_unavailable |
Model temporarily offline |
Use fallback models; check status.openrouter.ai |
Error Response Format
{
"error": {
"code": 401,
"message": "Invalid API key. Please check your API key and try again.",
"metadata": {
"provider_name": "Anthropic",
"raw": "..."
}
}
}
Diagnostic Script
#!/bin/bash
echo "=== OpenRouter Error Diagnostics ==="
# 1. Test authentication
echo -n "1. Auth: "
AUTH=$(curl -s -o /dev/null -w "%{http_code}" \
https://openrouter.ai/api/v1/auth/key \
-H "Authorization: Bearer $OPENROUTER_API_KEY")
[ "$AUTH" = "200" ] && echo "OK" || echo "FAIL (HTTP $AUTH)"
# 2. Check credit balance
echo -n "2. Credits: "
CREDITS=$(curl -s https://openrouter.ai/api/v1/auth/key \
-H "Authorization: Bearer $OPENROUTER_API_KEY" | \
jq -r '(.data.limit // 0) - .data.usage')
echo "\$$CREDITS remaining"
# 3. Test model availability
echo -n "3. Model: "
MODEL="openai/gpt-4o-mini"
EXISTS=$(curl -s https://openrouter.ai/api/v1/models | \
jq --arg m "$MODEL" '[.data[] | select(.id == $m)] | length')
[ "$EXISTS" -gt 0 ] && echo "$MODEL available" || echo "$MODEL NOT FOUND"
# 4. Test a minimal request
echo -n "4. Request: "
RESP=$(curl -s -w "\n%{http_code}" \
https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"openai/gpt-4o-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":1}')
HTTP=$(echo "$RESP" | tail -1)
[ "$HTTP" = "200" ] && echo "OK" || echo "FAIL (HTTP $HTTP)"
Python Error Handler
import os
from openai import OpenAI, APIError, AuthenticationError, RateLimitError, BadRequestError, APITimeoutError
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
max_retries=3, # Auto-retry 429 and 5xx
timeout=30.0,
default_headers={"HTTP-Referer": "https://my-app.com", "X-Title": "my-app"},
)
def safe_completion(messages, model="openai/gpt-4o-mini", **kwargs):
"""Completion with categorized error handling."""
try:
return client.chat.completions.create(
model=model, messages=messages, **kwargs
)
except AuthenticationError as e:
# 401: Bad or missing API key
raise SystemExit(f"AUTH ERROR: Check OPENROUTER_API_KEY. {e}")
except BadRequestError as e:
# 400: Bad model ID, invalid params, context too long
if "context_length" in str(e):
raise ValueError(f"Prompt too long for {model}. Trim or use larger-context model.")
raise ValueError(f"Bad request: {e}")
except RateLimitError:
# 429: SDK already retried max_retries times
raise RuntimeError("Rate limited after all retries. Wait or use more API keys.")
except APITimeoutError:
# Timeout: model too slow
raise TimeoutError(f"Model {model} timed out. Try streaming or a faster model.")
except APIError as e:
# 402, 5xx, other
if e.status_code == 402:
raise RuntimeError("Insufficient credits. Top up at openrouter.ai/credits")
raise RuntimeError(f"API error {e.status_code}: {e}")
Prevention Middleware
import requests
def validate_before_send(model: str, messages: list, max_tokens: int = 1024):
"""Pre-flight validation to catch common mistakes before API call."""
errors = []
# Check model exists
models = requests.get("https://openrouter.ai/api/v1/models").json()["data"]
model_ids = {m["id"] for m in models}
if model not in model_ids:
# Try to suggest correct ID
prefix = model.split("/")[0] if "/" in model else ""
suggestions = [m for m in model_ids if prefix and m.startswith(prefix)][:3]
errors.append(f"Model '{model}' not found. Did you mean: {suggestions}")
# Check messages format
if not messages or not isinstance(messages, list):
errors.append("messages must be a non-empty list")
for msg in messages:
if "role" not in msg or "content" not in msg:
errors.append(f"Each message needs 'role' and 'content': {msg}")
# Estimate context usage
total_chars = sum(len(str(m.get("content", ""))) for m in messages)
est_tokens = total_chars // 4
model_info = next((m for m in models if m["id"] == model), None)
if model_info:
ctx_limit = model_info["context_length"]
if est_tokens + max_tokens > ctx_limit:
errors.append(f"Estimated {est_tokens} + {max_tokens} max_tokens > {ctx_limit} context limit")
if errors:
raise ValueError("Pre-flight validation failed:\n" + "\n".join(f" - {e}" for e in errors))
Output
- A four-line diagnostic report: auth OK/FAIL, dollars of credit remaining, model availability in
/api/v1/models, and the HTTP status of a minimal live request
- Categorized exceptions from
safe_completion() — auth failures exit pointing at the key, 402s point at credit top-up, context overflows raise ValueError naming the model to trim for
- Pre-flight
ValueErrors from validate_before_send() listing every problem found (unknown model with suggested IDs, missing role/content fields, estimated-token overflow) before any API call is made
Examples
A healthy integration produces this from the Diagnostic Script:
=== OpenRouter Error Diagnostics ===
1. Auth: OK
2. Credits: $46.58 remaining
3. Model: openai/gpt-4o-mini available
4. Request: OK
Any FAIL line maps straight to a row in the Complete Error Reference table — e.g. 1. Auth: FAIL (HTTP 401) means regenerating the key at openrouter.ai/keys. More worked examples: references/examples.md.
Error Handling
| Scenario |
SDK Behavior |
Your Action |
| 429 rate limit |
Auto-retries with backoff |
Increase max_retries or add keys |
| 5xx server error |
Auto-retries with backoff |
Increase max_retries; add fallback models |
| 401 auth error |
Fails immediately (no retry) |
Fix API key and retry |
| 400 bad request |
Fails immediately (no retry) |
Fix request parameters |
| 402 no credits |
Fails immediately (no retry) |
Top up credits |
Enterprise Considerations
- The OpenAI SDK handles 429 and 5xx retries automatically -- configure
max_retries (default 2, recommend 3-5)
- Implement pre-flight validation to catch 400 errors before making API calls (saves money and time)
- Log error codes and rates to detect systematic issues (e.g., provider outages show as 502 spike)
- Build a status dashboard that checks both your error rates and status.openrouter.ai
- For 402 errors, implement credit balance monitoring with alerts at 20% remaining
References
- Examples | Errors
- Error Codes | Status
1---2name: openrouter-common-errors3description: Diagnose and fix common OpenRouter API errors. Use when encountering error codes, unexpected failures, or debugging API responses. Triggers: 'openrouter error', 'openrouter 401', 'openrouter 429', 'openrouter 402', 'fix openrouter'.4license: MIT5---6# OpenRouter Common Errors
7
8## Overview
9
10OpenRouter returns standard HTTP error codes plus OpenRouter-specific error codes in the response body. The most common: 401 (auth), 402 (credits), 429 (rate limit), 400 (bad request), and 5xx (upstream provider errors). Each error includes a `code` field and a human-readable `message`. This skill covers every common error, its root cause, and the exact fix.
11
12## Prerequisites
13
14- An OpenRouter API key (`sk-or-v1-...`) exported as `OPENROUTER_API_KEY` — see the `openrouter-install-auth` skill for setup
15- `curl` and `jq` to run the Diagnostic Script
16- Python 3.8+ with the OpenAI SDK for the categorized error handler; Node.js 18+ for the TypeScript typed-error classifier in the references
17- The `requests` package if you use the Prevention Middleware's pre-flight model check
18
19## Instructions
20
211. Identify the failure by HTTP status and `code` using the Complete Error Reference table (400/401/402/403/408/429/5xx each map to a specific fix).
222. Inspect the body per Error Response Format — `error.code`, `error.message`, and `error.metadata.provider_name` tell you whether OpenRouter or the upstream provider failed.
233. Run the Diagnostic Script: it checks auth via `GET /api/v1/auth/key`, computes remaining credits, verifies the model exists in `/api/v1/models`, and fires a minimal 1-token completion.
244. Wrap production calls with `safe_completion()` from Python Error Handler — `max_retries=3` auto-retries 429 and 5xx, and each exception class raises with its exact remedy.
255. Add `validate_before_send()` from Prevention Middleware to catch bad model IDs (with suggestions), malformed messages, and context overflows before spending money on a 400.
266. If errors persist across retries and providers, check [status.openrouter.ai](https://status.openrouter.ai) per the Error Handling table.
27
28## Complete Error Reference
29
30| HTTP | Error Code | Cause | Fix |
31|------|-----------|-------|-----|
32| 400 | `bad_request` | Malformed request body | Validate `messages` array format; ensure model ID includes provider prefix |
33| 400 | `invalid_model` | Model ID not found | Check model exists: `curl -s https://openrouter.ai/api/v1/models \| jq '.data[].id'` |
34| 400 | `context_length_exceeded` | Prompt + max_tokens > model limit | Reduce prompt size or use a larger-context model |
35| 400 | `invalid_tool_schema` | Tool definition has unsupported types | Use basic JSON Schema types only (string, number, boolean, object, array) |
36| 401 | `invalid_api_key` | Key malformed, revoked, or wrong | Regenerate at [openrouter.ai/keys](https://openrouter.ai/keys); key must start with `sk-or-v1-` |
37| 401 | `missing_api_key` | No `Authorization` header | Add `Authorization: Bearer sk-or-v1-...` header |
38| 402 | `insufficient_credits` | Credit balance is zero | Top up at [openrouter.ai/credits](https://openrouter.ai/credits) |
39| 402 | `credit_limit_reached` | Per-key credit limit hit | Increase key limit in dashboard or create new key |
40| 403 | `key_disabled` | Key was disabled by admin | Re-enable in dashboard or create new key |
41| 408 | `request_timeout` | Model took too long | Reduce max_tokens; use streaming; try faster model |
42| 429 | `rate_limit_exceeded` | Too many requests per interval | SDK auto-retries; increase `max_retries`; use multiple keys |
43| 502 | `provider_error` | Upstream provider returned error | Retry with backoff; try different provider via `provider.order` |
44| 503 | `model_unavailable` | Model temporarily offline | Use fallback models; check [status.openrouter.ai](https://status.openrouter.ai) |
45
46## Error Response Format
47
48```json
49{
50 "error": {
51 "code": 401,
52 "message": "Invalid API key. Please check your API key and try again.",
53 "metadata": {
54 "provider_name": "Anthropic",
55 "raw": "..."
56 }
57 }
58}
59```
60
61## Diagnostic Script
62
63```bash
64#!/bin/bash
65echo "=== OpenRouter Error Diagnostics ==="
66
67# 1. Test authentication
68echo -n "1. Auth: "
69AUTH=$(curl -s -o /dev/null -w "%{http_code}" \
70 https://openrouter.ai/api/v1/auth/key \
71 -H "Authorization: Bearer $OPENROUTER_API_KEY")
72[ "$AUTH" = "200" ] && echo "OK" || echo "FAIL (HTTP $AUTH)"
73
74# 2. Check credit balance
75echo -n "2. Credits: "
76CREDITS=$(curl -s https://openrouter.ai/api/v1/auth/key \
77 -H "Authorization: Bearer $OPENROUTER_API_KEY" | \
78 jq -r '(.data.limit // 0) - .data.usage')
79echo "\$$CREDITS remaining"
80
81# 3. Test model availability
82echo -n "3. Model: "
83MODEL="openai/gpt-4o-mini"
84EXISTS=$(curl -s https://openrouter.ai/api/v1/models | \
85 jq --arg m "$MODEL" '[.data[] | select(.id == $m)] | length')
86[ "$EXISTS" -gt 0 ] && echo "$MODEL available" || echo "$MODEL NOT FOUND"
87
88# 4. Test a minimal request
89echo -n "4. Request: "
90RESP=$(curl -s -w "\n%{http_code}" \
91 https://openrouter.ai/api/v1/chat/completions \
92 -H "Authorization: Bearer $OPENROUTER_API_KEY" \
93 -H "Content-Type: application/json" \
94 -d '{"model":"openai/gpt-4o-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":1}')
95HTTP=$(echo "$RESP" | tail -1)
96[ "$HTTP" = "200" ] && echo "OK" || echo "FAIL (HTTP $HTTP)"
97```
98
99## Python Error Handler
100
101```python
102import os
103from openai import OpenAI, APIError, AuthenticationError, RateLimitError, BadRequestError, APITimeoutError
104
105client = OpenAI(
106 base_url="https://openrouter.ai/api/v1",
107 api_key=os.environ["OPENROUTER_API_KEY"],
108 max_retries=3, # Auto-retry 429 and 5xx
109 timeout=30.0,
110 default_headers={"HTTP-Referer": "https://my-app.com", "X-Title": "my-app"},
111)
112
113def safe_completion(messages, model="openai/gpt-4o-mini", **kwargs):
114 """Completion with categorized error handling."""
115 try:
116 return client.chat.completions.create(
117 model=model, messages=messages, **kwargs
118 )
119 except AuthenticationError as e:
120 # 401: Bad or missing API key
121 raise SystemExit(f"AUTH ERROR: Check OPENROUTER_API_KEY. {e}")
122 except BadRequestError as e:
123 # 400: Bad model ID, invalid params, context too long
124 if "context_length" in str(e):
125 raise ValueError(f"Prompt too long for {model}. Trim or use larger-context model.")
126 raise ValueError(f"Bad request: {e}")
127 except RateLimitError:
128 # 429: SDK already retried max_retries times
129 raise RuntimeError("Rate limited after all retries. Wait or use more API keys.")
130 except APITimeoutError:
131 # Timeout: model too slow
132 raise TimeoutError(f"Model {model} timed out. Try streaming or a faster model.")
133 except APIError as e:
134 # 402, 5xx, other
135 if e.status_code == 402:
136 raise RuntimeError("Insufficient credits. Top up at openrouter.ai/credits")
137 raise RuntimeError(f"API error {e.status_code}: {e}")
138```
139
140## Prevention Middleware
141
142```python
143import requests
144
145def validate_before_send(model: str, messages: list, max_tokens: int = 1024):
146 """Pre-flight validation to catch common mistakes before API call."""
147 errors = []
148
149 # Check model exists
150 models = requests.get("https://openrouter.ai/api/v1/models").json()["data"]
151 model_ids = {m["id"] for m in models}
152 if model not in model_ids:
153 # Try to suggest correct ID
154 prefix = model.split("/")[0] if "/" in model else ""
155 suggestions = [m for m in model_ids if prefix and m.startswith(prefix)][:3]
156 errors.append(f"Model '{model}' not found. Did you mean: {suggestions}")
157
158 # Check messages format
159 if not messages or not isinstance(messages, list):
160 errors.append("messages must be a non-empty list")
161 for msg in messages:
162 if "role" not in msg or "content" not in msg:
163 errors.append(f"Each message needs 'role' and 'content': {msg}")
164
165 # Estimate context usage
166 total_chars = sum(len(str(m.get("content", ""))) for m in messages)
167 est_tokens = total_chars // 4
168 model_info = next((m for m in models if m["id"] == model), None)
169 if model_info:
170 ctx_limit = model_info["context_length"]
171 if est_tokens + max_tokens > ctx_limit:
172 errors.append(f"Estimated {est_tokens} + {max_tokens} max_tokens > {ctx_limit} context limit")
173
174 if errors:
175 raise ValueError("Pre-flight validation failed:\n" + "\n".join(f" - {e}" for e in errors))
176```
177
178## Output
179
180- A four-line diagnostic report: auth OK/FAIL, dollars of credit remaining, model availability in `/api/v1/models`, and the HTTP status of a minimal live request
181- Categorized exceptions from `safe_completion()` — auth failures exit pointing at the key, 402s point at credit top-up, context overflows raise `ValueError` naming the model to trim for
182- Pre-flight `ValueError`s from `validate_before_send()` listing every problem found (unknown model with suggested IDs, missing `role`/`content` fields, estimated-token overflow) before any API call is made
183
184## Examples
185
186A healthy integration produces this from the Diagnostic Script:
187
188```text
189=== OpenRouter Error Diagnostics ===
1901. Auth: OK
1912. Credits: $46.58 remaining
1923. Model: openai/gpt-4o-mini available
1934. Request: OK
194```
195
196Any FAIL line maps straight to a row in the Complete Error Reference table — e.g. `1. Auth: FAIL (HTTP 401)` means regenerating the key at openrouter.ai/keys. More worked examples: `references/examples.md`.
197
198## Error Handling
199
200| Scenario | SDK Behavior | Your Action |
201|----------|-------------|-------------|
202| 429 rate limit | Auto-retries with backoff | Increase `max_retries` or add keys |
203| 5xx server error | Auto-retries with backoff | Increase `max_retries`; add fallback models |
204| 401 auth error | Fails immediately (no retry) | Fix API key and retry |
205| 400 bad request | Fails immediately (no retry) | Fix request parameters |
206| 402 no credits | Fails immediately (no retry) | Top up credits |
207
208## Enterprise Considerations
209
210- The OpenAI SDK handles 429 and 5xx retries automatically -- configure `max_retries` (default 2, recommend 3-5)
211- Implement pre-flight validation to catch 400 errors before making API calls (saves money and time)
212- Log error codes and rates to detect systematic issues (e.g., provider outages show as 502 spike)
213- Build a status dashboard that checks both your error rates and [status.openrouter.ai](https://status.openrouter.ai)
214- For 402 errors, implement credit balance monitoring with alerts at 20% remaining
215
216## References
217
218- Examples | Errors
219- Error Codes | [Status](https://status.openrouter.ai)