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:
{
"error": "rate_limited",
"code": "rate_limited",
"retryAfterSeconds": 12
}Retry-After: 12
RateLimit-Limit: 600
RateLimit-Remaining: 0
RateLimit-Reset: 12
RateLimit-Policy: 600;w=60Some details differ by route:
- The server hold routes send
errorandretryAfterSecondsbut nocode. Match onerror: "rate_limited". - Manage-session and panorama responses send
Retry-Afteronly, without theRateLimit-*headers. - The readiness check sends
{"error": "rate_limited"}with no headers. - Browsers can read
Retry-Afteracross origins. TheRateLimit-*headers are visible to server callers only.
How to back off
- Wait for
Retry-Afterseconds, orretryAfterSecondswhen 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, 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 to learn about changes.
For how SeatLayer keeps inventory consistent when many buyers race for the same seats, see concurrency and performance. For every error code, see API errors.