# Rate limits

> The limits that actually apply, surface by surface: how many calls, what is counted, what the answer is over the limit and how your client backs off. A limit Rivr doesn't publish is not listed here.

- The error codes: https://developers.rivr.io/errors.md (`rate_limited`)
- Page in the portal: https://developers.rivr.io/rate-limits

## /api/public

- **Addresses.** `https://api.test.rivr.io/api/public/…` and `https://api.rivr.io/api/public/…`. The limit is counted in public-api, and since 2026-10-26 every call on api.rivr.io under `/api/public` goes through it: the limit applies to every call with a token. Behind it there are also protections per IP address that are not published limits.
- **The limit.** 1,200 calls a minute at an even pace, 20 a second, and at most 200 at once. A switch in Rivr's configuration decides whether the limit applies, for the whole of Rivr; build for it anyway.
- **Counted per.** Token. Not IP address and not client: the bucket is a hash of the whole token. A call without a token is not counted here.
- **Over the limit.** `429` with `Retry-After` in whole seconds, at least 1, and `application/problem+json` with `"error": "rate_limited"` and no `code`. No `RateLimit-*` header, so the `429` is the first sign. The call has not been carried out: it is refused before it reaches the service behind.

**Example: /api/public**

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

{
  "type": "https://www.rfc-editor.org/rfc/rfc6585#section-4",
  "title": "Too Many Requests",
  "status": 429,
  "error": "rate_limited",
  "detail": "Too many requests with this token. Retry after 1 s.",
  "requestId": "5b2f0c9e-8d41-4a7e-9f36-2c1d7e0a4b18"
}
```

## Tokens for organisation keys

- **Addresses.** `POST https://auth.test.rivr.tech/connect/token` (rivr-auth), and the same path on `https://auth.rivr.tech` in production.
- **The limit.** 60 calls a minute, in a fixed window of one minute. Nothing is queued.
- **Counted per.** The client's IP address, whatever the key.
- **Over the limit.** `429` with no `Retry-After` and no body. Wait until the window has passed, at most a minute. No token has been issued.

## Tokens for /api/public

- **Addresses.** `POST https://auth.test.rivr.io/connect/token` with JSON, for platform and merchant tokens.
- **The limit.** Rivr publishes no limit for this address. Still, don't fetch a token per call: a token lasts about an hour.
- **Over the limit.** Handle a `429` here as on the other paths: follow `Retry-After` if the answer has it, otherwise back off by the rules below.

## The account connection

- **Addresses.** `https://test.rivr.io/oauth/authorize` and `https://auth.test.rivr.io/oauth/…`, and in the sandbox also `https://test.rivr.io/api/clinic-connect/…`. In production the same paths without `.test`.
- **The limit.** Calls a minute, in a fixed window of one minute: `GET /oauth/authorize` 60, `POST /oauth/token` 60, `POST /oauth/deauthorize` 30, `POST /api/clinic-connect/sandbox/approve` 30 and `POST /api/clinic-connect/requests/{request}/cancel` 120, shared with the landing page's reads. Nothing is queued.
- **Counted per.** IP address, each route on its own, except `cancel`, which is counted together with the landing page's reads. Clients can share the address of Rivr's load balancer, so the count can be a shared one.
- **Over the limit.** `429` with no `Retry-After` and no body. Wait until the window has passed, at most a minute. Nothing has been done: no request has been saved, no code has been redeemed and no connection has been changed.
- **Good to know.** The sandbox first, production when Rivr confirms. On top of this there are ceilings on new attempts on `GET /oauth/authorize`, for all platforms together and per address. Over them the browser comes back to your address with `?error=temporarily_unavailable&state=…`, not with `429`.

## The developer portal

- **Addresses.** `https://developers.rivr.io`: the portal's own calls under `/bff/`, the documents as Markdown, `/llms.txt`, `/llms-full.txt`, `/sitemap.xml`, `/openapi/*.json`, `/postman/*.json`, `/errors.json`, `/events.json` and `/testdata.json`. The status page (`/status.md`, `/status.atom`, `/bff/status`) is not counted.
- **The limit.** 300 requests a minute for all clients together. Lower for two paths: the sign-in for `rivr login` (30 a minute for all clients together) and the relay's intake for `rivr listen` (240 a minute per relay address, for every sender to it together).
- **Counted per.** The connection's address. Behind the portal's ingress that is the ingress's address and not the client's, so in practice every client shares one count, and every sender to the same relay address shares the relay's.
- **Over the limit.** `429` with `Retry-After` in seconds and the headers `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset`, which the answers under the limit carry too. The body is JSON with `"error": "rate_limited"`.
- **Good to know.** The limit protects the portal and is not a quota for an integration. A `429` can therefore come from other people's traffic, even when you have made few calls yourselves: wait `Retry-After` seconds and try again. The documents change only with a deploy and are sent with `Cache-Control: public, max-age=300`, so fetch them once and keep them.

**Example: The portal**

```http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json; charset=utf-8
Retry-After: 60
x-ratelimit-limit: 300
x-ratelimit-remaining: 0
x-ratelimit-reset: 60

{
  "error": "rate_limited",
  "message": "Too many requests: wait the number of seconds in Retry-After"
}
```

## The MCP server

- **Addresses.** `POST https://developers.rivr.io/mcp`.
- **The limit.** 300 requests a minute per client, and 3,000 a minute for all MCP traffic together in one server instance. A tool call is one request.
- **Counted per.** The client's address. Claude's remote connectors share Anthropic's addresses, which is why the limit is generous.
- **Over the limit.** `429` with `Retry-After` and a JSON-RPC error without an `id`, with the code `-32000`.

**Example: The MCP server**

```http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json; charset=utf-8
Retry-After: 12

{
  "jsonrpc": "2.0",
  "id": null,
  "error": { "code": -32000, "message": "Too many requests: wait the number of seconds in Retry-After" }
}
```

## How a client backs off

- If the answer has `Retry-After`: wait that many seconds, then send the same call again, with the same `Idempotency-Key` and the same order id. A `429` has not done anything, so this is safe.
- Without `Retry-After`: wait longer for each attempt, for example 1, 2, 4, 8 and 16 seconds with some random jitter, and give up after a few attempts instead of carrying on in a loop. On the token endpoint for organisation keys: wait until the minute has passed.
- Only retry what the error catalog says a retry helps: `retryWhen` in `/errors.json` is `same_request` for `rate_limited` and Rivr's own failures.
- Make fewer calls: fetch a token once an hour and reuse it, spread bursts over time and take changes from webhooks instead of polling.
