# Rate limits

> One token bucket per key: a burst of 60 requests and 5 per second in Sandbox. The balance headers, the 429 and Retry-After, and what does or does not consume a token.

Each API key has its own **token bucket**, refilled continuously. A request consumes one token; an empty bucket gets the request refused with `429`.

## The values

| Environment | Burst (bucket capacity) | Sustained rate |
|---|---|---|
| Sandbox | 60 requests | 5 requests per second, per key |
| Production | Not published | Not published |

A key at rest can therefore send 60 requests at once, then carry on at 5 per second. Production limits are not published: an uncalibrated number would be a promise we have not measured. Read your key's actual value in `Noryo-RateLimit-Limit`.

The counter is central: it does not depend on the server that receives your request, and spreading your traffic does not get round it.

## The headers

| Header | Present on | Meaning |
|---|---|---|
| `Noryo-RateLimit-Limit` | `200` of all three routes, and `429` | Your key's burst capacity |
| `Noryo-RateLimit-Remaining` | `200` of all three routes, and `429` | The tokens left in your bucket |
| `Retry-After` | `429` | The whole number of seconds before a token is available again, at least 1 |

A successful `GET` read and errors other than `429` do not carry these headers, although they consume a token. Follow your balance on your `POST` responses, and slow down before being refused rather than after.

## The 429

```http
HTTP/1.1 429 Too Many Requests
content-type: application/problem+json
retry-after: 2
noryo-ratelimit-limit: 60
noryo-ratelimit-remaining: 0
```

```json
{
  "type": "https://developer.noryo.app/errors/rate_limited",
  "title": "Rate limited",
  "status": 429,
  "code": "rate_limited",
  "detail": "Too many requests. Retry after the delay given by Retry-After.",
  "request_id": "req_…"
}
```

**A 429 computes nothing and bills nothing**: the engine is not called, no event is written, the idempotency key is not reserved. Wait `Retry-After` seconds and send the same request again, with the same `Idempotency-Key`.

## What consumes a token

| Request | Token consumed |
|---|---|
| Successful create or refresh, replays included | yes |
| `GET` read, successful or not | yes |
| Validation refusal (`400 invalid_request`, `identifier_required`), `403 insufficient_scope`, `404`, `409`, `402`, `410` | yes |
| `401` (key missing, unknown, revoked), `403 account_inactive` | no |
| `400 invalid_json`, `400 invalid_idempotency_key`, `413`, `415`, unknown host | no |
| `429` | no: there was no token left |

As a rule: every request authenticated by a usable key consumes a token, whatever its response. The exact order of checks is described in [Requests](https://developer.noryo.app/en-GB/merchant/requests).

## Good practice

1. **Honour `Retry-After`.** It is the actual delay before a token is available.
2. **Smooth your sending.** The burst absorbs an occasional peak; the sustained rate is the speed you can keep up.
3. **Count per key.** Each key has its own bucket: two services sharing a key also share its rate.
4. **Keep the same `Idempotency-Key`** across attempts. See [Retries](https://developer.noryo.app/en-GB/merchant/operations/retries).

> **Note** A limiter failure never lets traffic through: the server returns `500 internal_error` rather than letting uncounted traffic pass. Nothing is computed or billed in that case.

---

Canonical page: https://developer.noryo.app/en-GB/merchant/rate-limits
