---
title: Rate limits
description: How many delegated-access calls your app can make, what happens when it goes over, and how to stay under.
---

Limits are per **environment**, live and test separately, so all the connections of one environment share them.

| Call | Limit |
| --- | --- |
| Token (code exchange and refresh together) | 100 per minute |
| Revoke | 50 per minute |
| Authorize (browser link) | 30 per minute, per user IP address |
| Authorize URL (`POST …/authorize-url`, server) | 300 per minute |

Every call also counts against your environment's overall API budget, the
same `requestsPerMinute` every ZevAuth endpoint shares.

Over a limit, the token and revoke endpoints return `429` with a
`Retry-After` header in seconds:

```json
{
  "error": "rate_limited",
  "error_description": "Over 100 token requests per minute for this environment. Retry in 23s.",
  "reason": "rate_limited",
  "error_reason": "rate_limited"
}
```

A browser authorize over the limit redirects back to your callback with
`error=temporarily_unavailable&error_reason=rate_limited`. The server call
answers `429` with `Retry-After` and the same JSON shape.

A `429` always carries `Retry-After`. Wait at least that long before the next
call.

## Staying under

- **Reuse access tokens.** They last 10 minutes. Refresh shortly before
  `expires_in` runs out, not on every call.
- **Serialise refreshes per connection.** One refresh at a time per
  `grant_id`. Two at once with the same token look like theft and revoke the
  connection's refresh tokens. See [Refresh tokens](/delegated-access/refresh-tokens).
- **Back off on 429.** Wait for `Retry-After`, then retry. Sustained 429s are
  usually a loop in your code.
- **Do not retry `invalid_grant`.** It will not succeed without the user.
