---
title: "Rate limits"
description: "pirouter rate limits by the per-minute quota of each upstream deployment, not by API key. This page explains the rules, how to tell the two kinds of 429 apart, how limits interact with model fallbacks, and how to retry."
canonical: "https://pirouter.ai/docs/rate-limits"
last-updated: "2026-10-17"
lang: "en"
source: "content/docs/en/(start)/rate-limits.mdx"
---

# Rate limits

pirouter rate limits by the per-minute quota of each upstream deployment, not by API key. This page explains the rules, how to tell the two kinds of 429 apart, how limits interact with model fallbacks, and how to retry.

pirouter rate limits per upstream deployment, not per key and not per workspace. Each upstream deployment has two per-minute quotas, requests per minute (RPM) and tokens per minute (TPM). When the deployment a request was routed to has used up its quota, the request gets a `429`.

## Two kinds of 429

You can receive two kinds of `429`, and they are handled differently:

|                           | pirouter                                               | Provider                                                               |
| ------------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------- |
| Who answers               | The pirouter gateway                                   | The provider, passed on as is by pirouter                              |
| `error.code`              | `rate_limited`                                         | The provider's own error body, without this code                       |
| `X-Pi-Provider`           | Absent                                                 | Present                                                                |
| `Retry-After`             | Always present, 1 to 60 seconds                        | Only if the provider sent one                                          |
| What pirouter already did | Nothing was forwarded or billed                        | Tried the model's other providers, and all of them failed              |
| What you do               | Wait `Retry-After` seconds, or switch to another model | Retry after `Retry-After` if present, otherwise with growing intervals |

Check whether `error.code` is `rate_limited`, or whether the `X-Pi-Provider` response header is present, to tell them apart.

## Rules

- **Counted per upstream deployment.** One model can have several upstream deployments, each with its own quota. Switching keys does not get around a limit.
- **Only RPM and TPM.** Both are per-minute quotas set on the deployment.

* **Calendar minutes.** Counters reset on each full UTC minute; it is not a sliding window.

- **RPM is counted on admission, TPM after the request ends.** A refused request still counts toward the minute's requests. Tokens are added after the request ends, as the actual input plus output; once the minute's total has reached the limit, the next request is refused. So one large request can take the minute's usage past the limit.

* **Only the chosen deployment is checked.** pirouter first picks one deployment by the [routing rules](https://pirouter.ai/docs/routing.md), then checks its quota. If that deployment is used up, the answer is `429`; pirouter does not move the request to another deployment of the same model.

## The `rate_limited` response

When pirouter rate limits a request it answers `429` with `error.type` `rate_limit_error` and `error.code` `rate_limited`. Branch on `error.code` in your code; do not match the text of `error.message`.

The response carries a `Retry-After` header: the number of seconds until the next full minute, from 1 to 60. Browser code can read it too.

The gateway writes this response itself, so it carries only `X-Pi-Trace-Id` and the CORS headers, not `X-Pi-Provider`. The request was not forwarded to any provider and costs nothing: the quota is checked before any balance is held.

In the console's **Logs**, such a request shows the outcome **Rate limited**, with the hint "The model's upstream capacity was saturated. Retry shortly, or switch to another model."

## A 429 from the provider

When a provider rate limits a request, pirouter moves on by itself: first to the model's other providers, then to your [fallback models](https://pirouter.ai/docs/fallbacks.md). You do not see any of this. You only receive the provider's `429` when every candidate failed and the last attempt was still a provider `429`. Its status and body are the provider's, it carries `X-Pi-Provider`, and a `Retry-After` from the provider is passed on as is.

## Rate limits and model fallbacks

- **pirouter's rate limit does not trigger a model fallback.** When a request is refused by a quota from the start, pirouter does not move on to the next model in `models`; it answers `429`. To get an answer right away, send the request to another model yourself.
- **A rate limit during a fallback ends the chain.** When an attempt has failed and the next candidate is refused by a quota, the answer is `429` `rate_limited` with `Retry-After`, not the error of the earlier attempt.

One request makes at most 5 attempts in total, across all models.

## How to retry

| You receive                                                      | What to do                                                                                                        |
| ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `429` `rate_limited`                                             | Wait `Retry-After` seconds and send again with fewer requests in flight. Switching keys does not help             |
| A provider `429` (with `X-Pi-Provider`)                          | pirouter has already tried other providers. Wait `Retry-After` if present, otherwise retry with growing intervals |
| `402`                                                            | Do not retry; [add credit](https://pirouter.ai/docs/billing.md#add-credit) first                                  |
| `500`, `502`, `503`, `504`, or `upstream_timeout` inside a `200` | Retry with growing intervals, measured in seconds                                                                 |

The official OpenAI and Anthropic SDKs retry a `429` on their own by default and read `Retry-After`. That is the SDK's behavior; the number of retries is set in the SDK.

For other errors, see [What to retry](https://pirouter.ai/docs/errors.md#what-to-retry).

## Not available today

- **Rate limits per key or per workspace.**
- **Remaining-quota headers.** Responses carry no `x-ratelimit-*` headers, so you cannot see how much of the minute's quota is left.
- **A concurrency cap.** Quotas count only requests and tokens per minute.

## Next steps

- [Model fallbacks](https://pirouter.ai/docs/fallbacks.md): Switch to another model when the one you asked for is unavailable.
- [Request and response headers](https://pirouter.ai/docs/headers.md): What each response header means and when it appears.
