An OpenRouter 429 does not always mean your OpenRouter account hit its limit. It can also mean the selected upstream provider is throttling requests. Save one complete failed response first: the HTTP status, response headers, and JSON body tell you which limit to fix.
Read One 429 Response Before Changing Anything
Classify the failure before buying credits, replacing a key, or adding retries. OpenRouter's typed fields and response headers are stronger evidence than the human-readable message, which can contain text forwarded from an upstream provider.
| Evidence | Most likely source | Next action |
|---|---|---|
HTTP 429 plus X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset | OpenRouter platform limit | Wait until reset, then reduce request rate or concurrency |
error.metadata.error_type is rate_limit_exceeded, with provider details such as provider_code | Upstream provider | Wait, allow another provider, or use a model fallback |
Retry-After is present | All attempted providers supplied a retry hint | Wait for that interval before the next attempt |
| HTTP 402 | Insufficient balance or an exhausted per-key credit cap | Add credit or change the key cap; retry backoff will not fix it |
HTTP 200, followed by an SSE error and finish_reason: "error" | Failure after streaming began | Treat the stream as failed and inspect the embedded error type |
OpenRouter's error and debugging reference defines the error.code, error.message, and optional error.metadata envelope, including error_type = "rate_limit_exceeded"; it also notes that successful responses normally omit X-RateLimit-*. Provider overload is typed separately as provider_overloaded and normally maps to 503.
Fix an OpenRouter-Level 429
An OpenRouter-level 429 is controlled by the platform quota attached to the account and model class. As verified against the official rate-limit documentation on July 31, 2026, free model variants ending in :free have both per-minute and daily limits.
| Free-model quota | Current limit |
|---|---|
| Requests per minute | 20 RPM |
| Daily requests with less than $10 in lifetime credit purchases | 50 RPD |
| Daily requests with at least $10 in lifetime credit purchases | 1,000 RPD |
The limit policy states that extra accounts or keys do not increase globally governed capacity, so replacing a valid key does not reset a platform rate limit.
Use the GET /api/v1/key endpoint to inspect usage and credit limits:
curl https://openrouter.ai/api/v1/key \
-H "Authorization: Bearer $OPENROUTER_API_KEY"
For a platform 429, use the error response's reset header and apply these changes in order:
- Stop immediate retries and wait until
X-RateLimit-Reset. - Lower concurrent requests, not just requests per second. A burst of parallel workers can cross the limit before any worker sees the first 429.
- Queue work behind a shared limiter so every worker does not wake and retry together.
- If the workload cannot fit the free-model quota, move that traffic to an appropriate paid model variant.
A negative balance or exhausted per-key credit cap should produce 402, while an upstream provider can still return 429 to a funded account. The OpenRouter pricing guide covers the separate cost and credit questions.
Fix "Provider Returned Error" 429
A provider-returned 429 means OpenRouter reached an upstream inference provider that would not accept the request at that moment. Look for the typed rate-limit value and provider metadata; adding OpenRouter credit does not create capacity at that provider.
The official limit reference says routing may already have tried alternative providers before returning the error and adds Retry-After when every attempted provider supplied a retry hint. The practical fixes are:
- Honor
Retry-Afterrather than immediately regenerating the same request. - Remove overly strict provider restrictions if they leave only one congested route.
- Verify that provider fallback is allowed for the request.
- Configure a model fallback when the exact model is less important than completing the task.
A funded Zed user saw an upstream limit on moonshotai/kimi-k2:free, and regenerating the key did not help. A Zed contributor explained:
“This is not a Zed error, this OpenRouter telling you that the upstream provider you're using is rate-limiting you.” Source: zed-industries/zed issue #35153
Honor a short Retry-After; use a free-model fallback only when completing the task matters more than keeping the exact model.
If the Error Appears in Janitor AI, Zed, or SillyTavern
Preserve the raw error, avoid repeated regeneration, and change the model or allowed route for provider-side failures. Re-enter a key only to correct client storage; it does not reset capacity.
Retry Without Creating a 429 Loop
Retry only rate-limit failures, cap the number of attempts, and prefer the server's requested delay. When no retry hint exists, use capped exponential backoff with jitter so parallel clients do not synchronize into another burst.
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
function retryDelayMs(response, attempt) {
const retryAfter = response.headers.get("retry-after");
if (retryAfter) {
const seconds = Number(retryAfter);
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);
const dateMs = Date.parse(retryAfter);
if (Number.isFinite(dateMs)) return Math.max(0, dateMs - Date.now());
}
const capMs = 30_000;
const exponentialMs = Math.min(capMs, 1000 * 2 ** attempt);
return Math.random() * exponentialMs; // Full jitter
}
async function createChatCompletion(body, maxAttempts = 4) {
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
const response = await fetch(
"https://openrouter.ai/api/v1/chat/completions",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
},
);
const raw = await response.text();
let payload;
try {
payload = raw ? JSON.parse(raw) : null;
} catch {
payload = null;
}
if (response.ok) return payload;
const isRateLimit =
response.status === 429 ||
payload?.error?.metadata?.error_type === "rate_limit_exceeded";
if (!isRateLimit || attempt === maxAttempts - 1) {
const error = new Error(payload?.error?.message || raw || `HTTP ${response.status}`);
error.status = response.status;
error.details = payload?.error;
throw error;
}
await sleep(retryDelayMs(response, attempt));
}
}
This function handles non-streaming responses; put shared queue or token-bucket concurrency control outside it so many sleeping workers cannot restart together.
Once Server-Sent Events begin with HTTP 200, the status cannot change to 429. The OpenRouter error reference says a later failure arrives in the stream with an error and finish_reason: "error"; mark the completion as failed and retry only when the embedded type is rate_limit_exceeded. Do not return accumulated text as a success unless the application explicitly supports partial results.
FAQ
What does 429 provider returned error mean in OpenRouter?
An upstream inference provider rejected the request because of its own rate or capacity limit. Confirm it with error.metadata.error_type and provider metadata.
Why do I get OpenRouter 429 when I still have credits?
A funded account can receive a provider-side 429; insufficient balance or a per-key credit cap is normally a 402 instead.
Does creating a new OpenRouter API key reset the rate limit?
No. Additional keys do not increase globally governed limits; replace one only to fix authentication or client storage.
How long should I wait before retrying OpenRouter?
Use Retry-After when it is present. For a platform limit, use X-RateLimit-Reset; without either hint, use capped exponential backoff with jitter and a small maximum attempt count.
Can OpenRouter return HTTP 200 and still fail with a 429?
Yes, when streaming has already begun. The HTTP status remains 200, while the SSE stream reports an error and ends with finish_reason: "error"; inspect the embedded error type to determine whether it was a rate limit.