# Rate limits

The API caps requests per fixed **60-second window**. Every response — success
or error — carries the current bucket's counters:

```
X-RateLimit-Limit:     120
X-RateLimit-Remaining: 117
X-RateLimit-Reset:     1715701260
```

- `Limit` — requests allowed in the current window.
- `Remaining` — what you have left in this window.
- `Reset` — unix timestamp (seconds) when the window rolls over.

## Default buckets

| Caller | Limit | Keyed by |
|---|---|---|
| **Authenticated PAT** | 120 rpm | PAT id |
| **Authenticated OAuth** | 120 rpm | `(client_id, account_id)` |
| **Anonymous** | 30 rpm | client IP |

Buckets are independent — a single account holding two PATs gets `2 × 120` rpm
across them.

## Per-endpoint overrides

OAuth public endpoints have their own dedicated buckets, keyed by IP, so a
spike on one endpoint can't starve the others:

| Endpoint | Limit |
|---|---|
| `GET /v1/oauth/authorize` | 30 rpm / IP |
| `POST /v1/oauth/token` | 60 rpm / IP |
| `POST /v1/oauth/revoke` | 60 rpm / IP |
| `POST /v1/oauth/introspect` | 120 rpm / IP |
| `POST /v1/oauth/register` | 5 rpm / IP |

## Going over the limit

The next request after the bucket is exhausted returns **`429 rate_limited`**:

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded; retry in 27s.",
    "details": { "bucket": "pat", "limit": 120, "window_seconds": 60 },
    "request_id": "req_…"
  }
}
```

Headers:

```
Retry-After: 27
X-RateLimit-Limit:     120
X-RateLimit-Remaining: 0
X-RateLimit-Reset:     1715701260
```

Wait at least `Retry-After` seconds before retrying.

## Recommendations

- Read `X-RateLimit-Remaining` proactively and slow your batch jobs down as
  it approaches 0 — don't wait for the 429.
- On `429`, back off until the window resets. Reset is a hard boundary, not
  a sliding penalty.
- For bulk reads, prefer `limit=100` (the max) and follow `meta.next_cursor`
  rather than re-issuing the same query with smaller pages.
- Buckets are **fail-open** if our rate-limit cache hiccups — the headers
  remain best-effort during such an outage.

## Anonymous calls

A few endpoints accept unauthenticated requests (`/.well-known/*`, OAuth
public endpoints). They share the 30 rpm-per-IP anonymous bucket. Any real
integration should be authenticated and will use the higher per-token quota.
