# Rate limits

> How many requests a key may send, what a 429 means and how to back off.

Source: https://rais.app/docs/rate-limits

The API limits how fast one key can send requests, so that a runaway loop on one integration cannot slow the others down. A request over the limit is refused with `429`, and nothing is processed.

## The limits

| Limit | Value |
| --- | --- |
| Requests per key | 60 per second on average, in bursts of up to 120 |
| Failed authentications | 30 per minute from one IP address |
| Payer receipt links (`receipt_url`) | 30 per minute from one client address |

The request budget is a token bucket: it holds up to 120 requests, and refills 60 every second. Every key has its own budget, so a sandbox key and a live key, or two services with two keys, never use each other's. The numbers may change: rely on the `Retry-After` header, not on these values. The API sends no `X-RateLimit-*` headers: a `429` and its `Retry-After` are the only signal.

## When you hit a limit

The answer is `429` with the code `rate_limit_exceeded` and a `Retry-After` header, in seconds:

~~~http
HTTP/1.1 429 Too Many Requests
Retry-After: 1
Rais-Request-Id: req_8f3a1c9d2b7e4a60c5d1

{"error":{"type":"rate_limit_error","code":"rate_limit_exceeded","message":"Too many requests. Slow down and retry after the delay in the Retry-After header.","param":null,"request_id":"req_8f3a1c9d2b7e4a60c5d1"}}
~~~

Repeated requests with keys that do not exist or are malformed are throttled too, per IP address: after 30 failures in a minute, the address answers `429` with the message "Too many failed authentication attempts. Retry later." for the rest of that minute. **While an address is throttled, every request from it is refused, even with a valid key.** A correct integration never meets this, unless a stale or revoked key is still being retried somewhere, for example by a job nobody stopped: when you revoke or roll a key, make sure the old one is no longer in use.

## Stay below the limits

- **Use webhooks, not polling.** One webhook replaces hundreds of `GET` requests. See [Webhooks](https://rais.app/docs/webhooks.md).
- **Back off.** On `429`, wait for `Retry-After`, then retry, with the same `Idempotency-Key` for `POST` requests. See [Idempotency and retries](https://rais.app/docs/idempotency.md).
- **Spread batch work.** A job that refunds or lists thousands of items should pace itself instead of sending a burst.
- **Page efficiently.** Use `limit=100` and walk the list with cursors. See [Pagination](https://rais.app/docs/pagination.md).
- **One key per service.** Services that share a key share its budget.

~~~python
import time
import requests

def get_with_backoff(url, headers, attempts=5):
    for attempt in range(attempts):
        response = requests.get(url, headers=headers, timeout=30)
        if response.status_code != 429:
            return response
        time.sleep(float(response.headers.get("Retry-After", 2 ** attempt)))
    return response
~~~
