OpenRouter 429 Rate Limit Exceeded — How to Diagnose and Fix It
A 429 from OpenRouter means a rate limit was hit — but the limit can come from two places: OpenRouter's own caps (especially the :free models and low-balance accounts) or the upstream provider OpenRouter routed to. This guide shows how to tell which, and how to make the error stop.
What does HTTP 429 mean on OpenRouter?
429 is the standard "Too Many Requests" status: you have exceeded a rate limit. The confusing part is that OpenRouter is a gateway to many model providers, so the limit you hit can originate in more than one place. The four common variants below are worth telling apart, because each has a different fix:
| Situation | What it means | Fix |
|---|---|---|
| 429 · free model | A :free model's strict daily / per-minute cap | Use the paid variant or add credits |
| 429 · low balance | Your rate ceiling is low because your credit balance is low | Add credits — the ceiling scales up |
| 429 · upstream | The provider OpenRouter routed to is rate-limiting / overloaded | Enable provider fallback + retry |
| 402 | Out of credits (a different error, not a rate limit) | Add credits at openrouter.ai/credits |
The key distinction: a 429 is temporary and retriable — you hit a rate limit. A 402 is not retriable — you are out of credits, and only topping up fixes it. When in doubt, read the error body (Step 2 below): it tells you the source.
5 steps to fix and prevent 429 errors
Rule out an outage first
Before touching your code, confirm the 429 is not an active incident — either on OpenRouter itself or on the upstream provider it routes to. A 429 (or a surge of them) during an outage means the right move is to wait and retry, not rewrite your retry logic.
Diagnosis checklist
- Open prismix.dev/service/openrouter — active incidents appear at the top.
- Also check status.openrouter.ai for OpenRouter's own incident page.
- Getting 429 on just one model? Try another (for example switch the upstream by routing to a different provider) — the overload may be limited to one downstream provider.
Read the error body to find the source
OpenRouter is OpenAI-compatible, so you use the OpenAI SDK pointed at https://openrouter.ai/api/v1 with your OPENROUTER_API_KEY. When a 429 comes back, log the JSON body and its error.metadata — if it names an upstream provider, the downstream model rate-limited you; if it does not, it is OpenRouter's own cap.
# Python — inspect a 429 to see where the limit came from
import os
from openai import OpenAI, APIStatusError
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
try:
resp = client.chat.completions.create(
model="deepseek/deepseek-r1:free",
messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)
except APIStatusError as e:
if e.status_code == 429:
# error.metadata usually names the upstream provider when the
# limit came from downstream; if absent, it is OpenRouter's cap.
print("429 rate limited. Body:", e.response.text)
print("Retry-After:", e.response.headers.get("Retry-After"))
else:
raise // TypeScript — same idea
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.OPENROUTER_API_KEY,
});
try {
const resp = await client.chat.completions.create({
model: 'deepseek/deepseek-r1:free',
messages: [{ role: 'user', content: 'Hello' }],
});
console.log(resp.choices[0].message.content);
} catch (e) {
if (e instanceof OpenAI.APIError && e.status === 429) {
// e.error.metadata names the upstream provider when the limit
// was passed through; otherwise it is OpenRouter's own cap.
console.warn('429 rate limited', e.error);
console.warn('Retry-After', e.headers?.['retry-after']);
} else {
throw e;
}
} If it is a :free model, switch or add credits
The :free variants share a small capacity pool and carry strict per-minute and per-day caps — they are meant for testing, not production. The fastest fix is to drop the :free suffix and use the paid variant. Adding credits also helps twice over: your overall rate ceiling scales with your balance, and the daily allowance on free models scales with your lifetime credits purchased.
# Heavily rate-limited (shared free pool):
model = "deepseek/deepseek-r1:free"
# Paid variant — much higher limits, pay-per-token:
model = "deepseek/deepseek-r1"
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "Hello"}],
) - Raise your ceiling: add credits at openrouter.ai/credits. A near-zero balance keeps you in the lowest rate bracket even on paid models.
- Watch the exact limits: published caps change over time — check your current per-key limits in the OpenRouter dashboard rather than hard-coding a number.
Add exponential backoff (respect Retry-After)
Transient 429s clear on their own if you wait and retry. Wrap the call in a retry loop that honors the Retry-After header when OpenRouter sends one, and otherwise backs off exponentially with jitter, capped at 60 seconds:
import os, time, random
from openai import OpenAI, APIStatusError
client = OpenAI(base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"])
def create_with_backoff(max_attempts: int = 6, **kwargs):
"""Retry on 429 with Retry-After, else exponential backoff + jitter."""
for attempt in range(max_attempts):
try:
return client.chat.completions.create(**kwargs)
except APIStatusError as e:
if e.status_code != 429 or attempt == max_attempts - 1:
raise # 402/401/400 are not retriable; give up on last try
retry_after = e.response.headers.get("Retry-After")
wait = float(retry_after) if retry_after else min(
60.0, (2 ** attempt) + random.uniform(0, 1))
print(f"429 — retry {attempt + 1}/{max_attempts - 1} in {wait:.1f}s")
time.sleep(wait)
response = create_with_backoff(
model="deepseek/deepseek-r1",
messages=[{"role": "user", "content": "Hello"}],
) // TypeScript async version
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.OPENROUTER_API_KEY,
});
async function createWithBackoff(
params: OpenAI.ChatCompletionCreateParamsNonStreaming,
maxAttempts = 6,
) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await client.chat.completions.create(params);
} catch (e) {
if (!(e instanceof OpenAI.APIError) || e.status !== 429) throw e;
if (attempt === maxAttempts - 1) throw e;
const retryAfter = Number(e.headers?.['retry-after']);
const wait = retryAfter
? retryAfter * 1000
: Math.min(60_000, (2 ** attempt) * 1000 + Math.random() * 1000);
console.warn(`429 — retry ${attempt + 1} in ${(wait / 1000).toFixed(1)}s`);
await new Promise(r => setTimeout(r, wait));
}
}
} Enable provider routing and fallbacks
When the 429 comes from an upstream provider, the best fix is to let OpenRouter route around it. Set provider.allow_fallbacks so a rate-limited or overloaded provider is automatically retried on another that serves the same model. You can also pass a models array to fall back across different models in order. With the OpenAI SDK these OpenRouter extensions go in extra_body (Python) or as extra request fields (TypeScript):
# Python — OpenRouter provider routing + model fallback
resp = client.chat.completions.create(
model="deepseek/deepseek-r1",
messages=[{"role": "user", "content": "Hello"}],
extra_body={
# Fall back across models in order if one is unavailable:
"models": [
"deepseek/deepseek-r1",
"anthropic/claude-3.5-sonnet",
],
# Provider-level routing: let OpenRouter retry another upstream
# that serves the same model when one is rate-limited / down.
"provider": {
"allow_fallbacks": True,
},
},
) // TypeScript — same routing fields on the request body
const resp = await client.chat.completions.create({
model: 'deepseek/deepseek-r1',
messages: [{ role: 'user', content: 'Hello' }],
// OpenRouter extensions (not in the base OpenAI types):
// @ts-expect-error
models: ['deepseek/deepseek-r1', 'anthropic/claude-3.5-sonnet'],
provider: { allow_fallbacks: true },
}); - Combine layers: provider fallback handles a single bad upstream; client-side backoff (Step 4) handles the case where every route is momentarily busy. Use both.
- Always honor Retry-After: when OpenRouter or an upstream sends the header, waiting exactly that long is more reliable than guessing.
Get an email the next time OpenRouter goes down
Outage alerts for OpenRouter, straight to your inbox. No account needed, unsubscribe in every email.
Watching more than one service? A free account covers 5 services + a daily digest — and Pro is currently free.
FAQ
Why do free OpenRouter models rate-limit so fast?
The :free variants share a small, heavily-used capacity pool, so OpenRouter applies strict per-minute and per-day caps to keep them available for everyone. Those limits are far below the paid models and are reached quickly under any real traffic. Free daily limits also scale with the lifetime credits you have purchased, so accounts with more credit history get a higher free ceiling. For anything past light testing, use the paid variant or add credits.
Does adding credit increase my rate limit?
Yes. OpenRouter scales your rate ceiling with your credit balance — more credits means a higher requests-per-minute allowance. A near-zero balance keeps you in the lowest bracket, where 429s appear even on paid models. Topping up raises the limit almost immediately, and it also lifts the separately-capped daily allowance on the free models.
Is the 429 from OpenRouter or the model provider?
Either — and the error body tells you which. As a gateway, OpenRouter can return a 429 from its own limits (free-model caps or your balance-based ceiling) or pass one through from the upstream provider it routed to. Inspect the JSON error.metadata: when it names an upstream provider, the limit came from downstream; when it is absent, it is usually OpenRouter's own cap.
How do I auto-fallback to another provider?
Add OpenRouter's routing to the request body. Set provider.allow_fallbacks to true so a rate-limited or down upstream is retried automatically on another provider serving the same model, and optionally pass a models array to fall back across models. With the OpenAI SDK these go in extra_body (Python) or as extra request fields (TypeScript). Pair it with backoff that respects Retry-After.
Difference between 429 and 402 on OpenRouter?
A 429 is a rate limit — too many requests, or a free-model / low-balance cap — and should be retried with backoff. A 402 (Payment Required) means you are out of credits: your balance cannot cover the request and retrying will not help. Fix a 402 by adding credits at openrouter.ai/credits; fix a 429 by slowing down, backing off, switching off a :free model, adding credits, or enabling provider fallback.