# Rate limits

Two sliding-window limiters guard the API, and a request has to pass both.
Tripping either limiter starts a block period longer than the window. Back off
on the first rejection; retrying during the block does not shorten it.

## v3 (`/api/v3/*`)

Every v3 endpoint sits behind both buckets. The table says what each one is
keyed by; on `DELETE` the second bucket is shared by every token at your address:

| Method | First bucket | Keyed by | Second bucket | Keyed by |
| --- | --- | --- | --- | --- |
| `GET` | 3,000 per 5 minutes | token | 3,000 per 5 minutes | token |
| `POST` | 500 per minute | token | 200 per minute | token |
| `PATCH` | 100 per minute | token | 200 per minute | token |
| `DELETE` | 200 per 10 minutes | token | 200 per 10 minutes | client IP |

So one token gets 3,000 `GET` per 5 minutes and 200 `POST` and 200 `PATCH` per
minute. Where a limit is keyed by IP, every token calling from the same address
shares it — plan for that on a NAT'd fleet.

Limits are the same on every plan. If your integration needs more, contact
support with what you are building.

Every admitted v3 response carries your remaining budget:

| Header | |
| --- | --- |
| `X-RateLimit-Limit` | requests allowed in the window |
| `X-RateLimit-Remaining` | requests left in it |
| `X-RateLimit-Reset` | seconds until the window resets |

Pace off `X-RateLimit-Remaining` rather than waiting for a rejection. On a
`429`, read `Retry-After` and ignore any `X-RateLimit-*` values it carries.

### Over the limit

`GET`, `POST`, `PATCH` and `DELETE` all answer `429`, with `Retry-After` and an
RFC 7807 body:

```http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 3600
```

```json
{ "type": "rate_limited", "title": "Rate limit exceeded", "status": 429,
  "detail": "Rate limit exceeded. Try again in 3600 seconds.",
  "instance": "/tasks", "code": "rate_limited", "retryAfter": 3600 }
```

The header and the body's `retryAfter` carry the same number of seconds. Read
either. The body is RFC 7807 in shape and is served as `application/json`, like
every other response — see [Errors](/errors).

## v1.0 (`/api/v1.0/*`)

| Method | Per token | Per IP |
| --- | --- | --- |
| `GET` | 1,000 per 5 minutes | 200 per minute |
| `POST` | 50 per minute | 100 per minute |
| `PUT` | 50 per minute | 100 per minute |
| `DELETE` | 10 per minute | 200 per 10 minutes |
| `POST /messages` | 500 per 5 minutes | 100 per minute |

Over the limit:

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 3600
```

```json
{ "message": "Rate limit exceeded. Retry after the number of seconds in the Retry-After header.",
  "code": 429 }
```

`Retry-After` is the remaining **block** time, not the remaining window, and the
blocks are long: 60 minutes after exhausting the per-token `GET` or `POST
/messages` bucket, 10 minutes after the other per-token buckets, 10 minutes
after the per-IP `GET` bucket and 60 minutes after the other per-IP ones.

## `POST /oauth/token`

Both token endpoints have their own per-IP limiter, and the two are not the
same:

| Endpoint | Limits |
| --- | --- |
| `api.niftypm.com/oauth/token` — the [OAuth 2.1](/authorization) server | **120 per minute** |
| `openapi.niftypm.com/oauth/token` | **120 per minute** and **1,000 per hour** |

Neither has a block period — a client that briefly spikes recovers within one
window. Over the limit both answer `429` with `Retry-After` and an RFC 6749
body:

```json
{ "error": "rate_limited",
  "error_description": "Too many token requests. Retry after 42 seconds.",
  "retryAfter": 42 }
```

## Backing off

- **Respect `Retry-After` / `retryAfter`.** It is an exact number of seconds, not an estimate.
- If neither is present, sleep 1 second and double on each attempt, giving up after 5 retries.
- Add jitter, so a fleet of workers that tripped together does not retry together.
- Never blind-retry a `POST` without an `Idempotency-Key` — see [Getting started](/getting-started).
- Spend fewer requests rather than pacing more: one paginated `LIST` beats N `GET`s by id, and `?expand=` beats a follow-up request per relation.
