# Rate limits

Every response carries the current state of the bucket it charged:

```http
RateLimit-Limit: 600
RateLimit-Remaining: 587
RateLimit-Reset: 1789459620
```

On a refusal you also get `Retry-After`, in seconds, and a [rate_limited](/errors/rate_limited) problem body.

// The headers are on EVERY response, not just refusals — a client that can see its remaining budget can slow down before it is cut off. One that only learns at the 429 has no way to behave well.

## The buckets

| bucket | limit |
|---|---|
| per credential, all requests | 600 / 5 min |
| per credential, writes | 120 / 5 min |
| `POST /oauth/token` | 60 / min / IP |
| `POST /oauth/register` | 10 / hour / IP |
| approval code lookup | 10 / min / user, 30 / min / IP |
| `POST /keys` | 10 / day / user |
| `POST /webhooks` | 20 / hour / user |
| per IP, at the edge | 20 req/s, burst 100 |

Windows are fixed, not sliding. A burst that straddles a boundary can spend two windows' worth; that is a deliberate trade for counters that survive a restart.

## Behaving well

1. **Read `RateLimit-Remaining` and slow down.** Do not wait for the 429.
2. **On 429, sleep `Retry-After`.** Not a guess, not immediately.
3. **Back off exponentially on 5xx**, with jitter, so a restart does not gather every client into one thundering retry.
4. **Use `updated_since` rather than re-reading everything.** A full re-read on a timer is the usual reason an integration hits the limit at all.
5. **Prefer a webhook to a tight poll** — but keep a slow reconciliation poll, because [inbound delivery is best-effort](/webhooks).

## If you need more

The limits are tunable and currently generous relative to what real integrations use. If you are hitting one legitimately, say what you are building.
