---
title: "Rate limits"
description: "Request budgets for the SeatLayer API, how each one is counted, the 429 response and headers, and how to back off safely."
---

SeatLayer limits a few high-traffic routes so one caller cannot starve others
of inventory. Most Server API routes have no request budget. Each limit below
is a fixed window: the count starts with the first request and resets when the
window ends.

## Server API budgets

| Budget | Limit | Counted per | Applies to |
|---|---|---|---|
| Server holds | 600 per minute | Secret key | `POST /v1/events/:key/hold`, `/extend`, `/best-available`, `/best-available-block`, `/best-available-book` |
| Seasons API | 600 per minute | Secret key | Every `/v1/seasons/*` route, plus `POST /v1/webhooks/:id/rotate-secret` |
| Manage sessions | 300 per minute | `mse_…` token | Every route called with an event manage token |
| Panorama manifests | 10 per day | Account and chart | `POST /v1/charts/:chartId/panorama-manifests` |
| Readiness | 30 per minute | IP address | `GET /health/ready` |

The five hold routes share one budget per key. Releasing a hold does not count
against it, so returning inventory is never refused for rate. Season routes
called from a signed-in dashboard session skip the Seasons API budget.

Readiness is counted in memory on each server instance, so treat it as a rough
guard for health checks, not an exact number.

## Buyer and public budgets

These routes are called from browsers, so they are counted per IP address.

| Budget | Limit | Counted per | Applies to |
|---|---|---|---|
| Buyer holds | 120 per minute | IP address | Hold and Best Available calls on `/pub/*` |
| Buyer reads | 3,000 per minute | IP address | Seat map and availability reads on `/pub/*` |
| Season buyer sessions | 30 per minute | IP address and Season | `POST /pub/seasons/:key/sessions` |
| Performance Group buyer sessions | 30 per minute | IP address and group | `POST /pub/performance-groups/:key/sessions` |
| Season renewal links | 60 per minute | IP address | `/pub/season-renewals/*` |

A buyer session is counted per IP and per Season or group, so many buyers
behind one office or venue network do not lock each other out of different
events.

## The 429 response

A refused request answers `429 Too Many Requests`:

```json title="429 response"
{
  "error": "rate_limited",
  "code": "rate_limited",
  "retryAfterSeconds": 12
}
```

```http title="Headers"
Retry-After: 12
RateLimit-Limit: 600
RateLimit-Remaining: 0
RateLimit-Reset: 12
RateLimit-Policy: 600;w=60
```

Some details differ by route:

- The server hold routes send `error` and `retryAfterSeconds` but no `code`.
  Match on `error: "rate_limited"`.
- Manage-session and panorama responses send `Retry-After` only, without the
  `RateLimit-*` headers.
- The readiness check sends `{"error": "rate_limited"}` with no headers.
- Browsers can read `Retry-After` across origins. The `RateLimit-*` headers are
  visible to server callers only.

## How to back off

- Wait for `Retry-After` seconds, or `retryAfterSeconds` when the header is
  missing, before trying again. Do not retry in a tight loop.
- A 429 means the request did nothing: no hold was created and no seat
  changed. Retrying after the wait is safe.
- Spread bursts. An on-sale that sends every buyer's hold through one secret
  key uses the same 600 per minute budget. Let the buyer's browser hold seats
  through the [seating chart](/buyer-sdk/seating-chart/), which uses per-IP
  budgets, and keep server holds for phone, box office, and headless sales.
- Use separate keys for separate systems. A reporting job on the same key as
  checkout competes for the Seasons budget.
- Cache reads. Event lists, charts, and reports have no budget, but polling
  them every second adds load without new data. Use
  [webhooks](/webhooks/events/) to learn about changes.

For how SeatLayer keeps inventory consistent when many buyers race for the
same seats, see [concurrency and performance](/platform/concurrency-performance/).
For every error code, see [API errors](/server-api/errors/).