Nano GPT logo
NanoGPT

Private AI

Back to Blog

OpenRouter 429 Rate Limits: What They Mean and How to Recover

Jul 22, 2026

An OpenRouter 429 Too Many Requests response does not automatically mean that OpenRouter is down. It means the request could not be served within an available rate or capacity limit.

That limit may belong to OpenRouter, to the provider running the model, or to a free-model allowance. Which one you hit determines the next step. Waiting may be enough; other cases call for lower traffic, another provider, a different model, or an independent API gateway.

What an OpenRouter 429 can mean

OpenRouter's current limits documentation describes two main sources of 429 responses.

An OpenRouter platform limit

Free model variants, whose IDs end in :free, have request caps. As of July 22, 2026, OpenRouter publishes a limit of 20 requests per minute. For these free variants, the daily allowance is 50 requests for accounts that have purchased less than $10 in credits and 1,000 requests after at least $10 in lifetime credit purchases. These figures can change, so check the linked limits page before relying on them.

OpenRouter also applies DDoS protection to unusually heavy traffic. Its documentation says creating another API key does not increase account- or platform-wide capacity.

Paid model variants do not have the free-model request cap, but they can still be affected by provider capacity and protective limits.

An upstream provider limit

OpenRouter routes a model request to an underlying inference provider. That provider may be rate-limited, temporarily full, or unable to accept more work for the requested model.

OpenRouter already retries eligible providers for the same model through its provider routing. Its default routing considers price and recent availability, with other providers available as fallbacks. If you have restricted the eligible providers too tightly, however, there may be nowhere else for the request to go.

You can also configure fallback models. That is a broader change: instead of finding another provider for the same model, OpenRouter tries a different model when the first one cannot complete the request.

A 402 is a different problem

An exhausted account balance or per-key credit limit is normally a 402 Payment Required, not a 429. Backoff will not fix it; check the account balance and the key's credit limit instead.

Read the response before retrying

The status code alone does not tell the whole story. When OpenRouter itself returns a platform-level 429, the response may include:

  • X-RateLimit-Limit
  • X-RateLimit-Remaining
  • X-RateLimit-Reset

When all attempted providers return a retry hint, the response may also include Retry-After. Honor it when it is present rather than choosing a shorter delay yourself.

The documented error metadata may include error.metadata.provider_code, which helps identify an upstream-provider rejection. Log the status, headers, error type, requested model, and your own request ID. Do not log API keys, private prompts, or full responses by default.

Streaming needs separate handling. If the limit is hit after a stream has already started, the HTTP response may remain 200 because its headers were already sent. OpenRouter documents an SSE chunk containing an error payload and finish_reason: "error". A streaming client should inspect chunks for both instead of watching only the initial HTTP status.

Recover without making the limit worse

1. Wait when the response tells you to wait

Use Retry-After when available. Otherwise, retry with exponential backoff and a small amount of random jitter. For example, a client might wait roughly 1, 2, and 4 seconds rather than sending three immediate retries.

Keep the retry count bounded. Endless retries increase traffic during a capacity problem and make the eventual failure slower and more expensive.

2. Reduce the pressure you control

If retries keep failing, lower concurrency rather than merely adding a longer queue. A burst of 100 simultaneous requests can keep colliding with the same limit even when the average request rate looks reasonable.

Long outputs also hold capacity for longer. Where the task allows it, request shorter answers, split large background jobs into smaller waves, and avoid launching every queued job at once.

3. Use OpenRouter's built-in fallbacks

Before adding another service, make sure your OpenRouter configuration is not preventing its own recovery features from working.

  • Allow provider fallbacks unless you genuinely require one provider.
  • Avoid provider filters that leave only one eligible route.
  • Add a tested fallback model when continuity matters more than using one exact model.
  • Confirm that the fallback supports the context length, tools, structured output, and other features your application needs.

A model fallback can change style, latency, price, and behavior. Treat it as a product decision, not just an infrastructure switch.

4. Add a separate backup API path

Provider fallback and gateway fallback protect against different failures.

OpenRouter's provider routing can move a request between inference providers while the request still enters through OpenRouter. That is useful for provider outages and capacity problems. It does not give your application another entry point if the OpenRouter endpoint, your OpenRouter account, or an OpenRouter-level limit is the part you cannot use.

For that, your application needs a second gateway with its own endpoint, API key, and available balance. NanoGPT exposes an OpenAI-compatible API at:

https://nano-gpt.com/api/v1

Do not simply change the URL during an incident and hope the rest matches. Choose and test a suitable model on each gateway in advance. Model IDs, available features, limits, and routing behavior may differ.

A two-gateway example

If you already use the OpenAI SDK, you can keep separate clients and select the model configured for each service. This non-streaming TypeScript example retries transient failures before moving to NanoGPT:

import OpenAI from "openai";
import type { ChatCompletionMessageParam } from "openai/resources/chat/completions";

function requiredEnv(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error(`Missing ${name}`);
  return value;
}

const [primaryGateway, secondaryGateway] = [
  {
    client: new OpenAI({
      baseURL: "https://openrouter.ai/api/v1",
      apiKey: requiredEnv("OPENROUTER_API_KEY"),
      maxRetries: 0,
      timeout: 20_000,
    }),
    model: requiredEnv("OPENROUTER_MODEL"),
  },
  {
    client: new OpenAI({
      baseURL: "https://nano-gpt.com/api/v1",
      apiKey: requiredEnv("NANOGPT_API_KEY"),
      maxRetries: 0,
      timeout: 20_000,
    }),
    model: requiredEnv("NANOGPT_MODEL"),
  },
] as const;

const retryableStatuses = new Set([408, 429, 502, 503, 504]);
const wait = (milliseconds: number) =>
  new Promise((resolve) => setTimeout(resolve, milliseconds));

function isRetryable(error: unknown): boolean {
  return error instanceof OpenAI.APIConnectionError ||
    (error instanceof OpenAI.APIError &&
      retryableStatuses.has(error.status ?? 0));
}

function retryDelay(error: unknown, attempt: number): number {
  if (error instanceof OpenAI.APIError) {
    const retryAfter = error.headers?.get("retry-after");
    const retryAfterSeconds = Number(retryAfter);
    if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds >= 0) {
      return retryAfterSeconds * 1_000;
    }

    const retryAt = Date.parse(retryAfter ?? "");
    if (Number.isFinite(retryAt)) {
      return Math.max(0, retryAt - Date.now());
    }
  }

  return Math.min(1_000 * 2 ** attempt, 8_000) + Math.random() * 250;
}

async function requestGateway(
  gateway: typeof primaryGateway | typeof secondaryGateway,
  messages: ChatCompletionMessageParam[],
) {
  for (let attempt = 0; ; attempt += 1) {
    try {
      return await gateway.client.chat.completions.create({
        model: gateway.model,
        messages,
      });
    } catch (error) {
      if (!isRetryable(error) || attempt >= 2) throw error;
      await wait(retryDelay(error, attempt));
    }
  }
}

export async function createCompletion(
  messages: ChatCompletionMessageParam[],
) {
  try {
    return await requestGateway(primaryGateway, messages);
  } catch (primaryError) {
    if (!isRetryable(primaryError)) throw primaryError;

    try {
      return await requestGateway(secondaryGateway, messages);
    } catch (secondaryError) {
      throw new AggregateError(
        [primaryError, secondaryError],
        "Both AI gateways failed",
      );
    }
  }
}

The SDK's own retries are disabled here so they do not multiply the application's three attempts. Adjust the timeout, retry count, and eligible statuses for your workload. A production implementation should also monitor the route used and add a circuit breaker: after repeated failures, temporarily stop sending new traffic to the unhealthy gateway.

For some applications, it is better to fail over new requests for a short period than to try both gateways on every call. This prevents a slow or rate-limited primary from adding delay to every user request.

Be careful with automatic replay

A retry is safest when a request failed before any model output or other work began. The situation becomes less certain after streaming starts, a tool runs, or a billable generation may already have been accepted.

Blindly replaying those requests can create:

  • Duplicate model output and charges
  • A tool or external action running twice
  • Two messages appearing in the same conversation
  • Conflicting records in your application

Give each logical operation a stable application request ID, use idempotency support where the destination offers it, and deduplicate side effects in your own system. Do not automatically replay a partially completed stream as if nothing happened. In many user-facing products, the safest option is to show the partial result and offer a manual retry.

Match the fallback to the failure

What you observeBest first response
Retry-After is presentWait for that period before retrying
Free-model cap is exhaustedWait for reset, buy credits to raise the published daily allowance, or use a paid model variant
Provider capacity or provider code in the errorAllow more providers or use a tested fallback model
Repeated 429s during a traffic burstReduce concurrency and add bounded backoff
OpenRouter endpoint or account path is unavailableRoute new requests through an independent gateway
Error arrives after streaming beganDo not blindly replay; handle the partial request explicitly
402 Payment RequiredAdd balance or adjust the API key's credit limit

Prepare before the first incident

A second gateway is only useful if it is ready before you need it.

  1. Create separate API keys and keep enough balance on both services.
  2. Pick a primary and fallback model for each important workload.
  3. Test prompts, tools, structured outputs, context lengths, and streaming on both.
  4. Decide which errors may trigger retries, model fallback, or gateway failover.
  5. Cap attempts so one user request cannot create an uncontrolled retry chain.
  6. Monitor which gateway and model ultimately served each request.
  7. Run a planned failover test rather than waiting for a real incident.

Use OpenRouter's provider and model fallbacks for provider-side 429 errors. Keep a second gateway for the separate case where retrying through the same API path is no longer enough.

You can inspect NanoGPT's current model catalog at /api/v1/models, create an API key on the API page, and use the OpenAI-compatible base URL shown above for a tested secondary route.

Milan de Reede

Milan de Reede

CEO & Co-Founder

milan@nano-gpt.com
Back to Blog