# Rate limits

> Limits keep the API fast for everyone. Normal app and server traffic stays far below them. When you go over, you get a `429` and, on most endpoints, a `Retry-After` header.

Source: https://straitlink.in/docs/rate-limits/

## How a limit works

Each limit is a bucket: it allows a burst of requests at once, then refills at a steady rate. While the bucket is empty you get:

```txt
HTTP/1.1 429 Too Many Requests
Retry-After: 60

{ "error": "rate limited" }
```

When `Retry-After` is sent, wait that many seconds before trying again. Where it isn't, back off for a second or two and retry. Every `429` body is listed in the [Error reference](https://straitlink.in/docs/errors/).

## Limits by endpoint

| Endpoints | Burst | Then | Counted per | `Retry-After` |
|---|---|---|---|---|
| App and website calls: `POST /v1/match`, `/v1/referrer`, `/v1/resolve`, `/v1/open`, `/v1/event`, `/v1/handoff/claim`, `/v1/store-sheet`, and `GET /v1/simulate` | 60 | 1 a second | IP address | not sent |
| Paste handoff: `POST /v1/handoff/claim` (on top of the line above) and opening a handoff link | 10 | 10 a minute | IP address | 60 |
| Analytics: `GET /v1/analytics` | 30 | 1 every 2 seconds (30 a minute) | workspace | 2 |
| Referral report: `GET /v1/referrals` | 30 | 1 every 2 seconds (30 a minute) | workspace | 2 |
| QR codes: `GET /v1/links/{slug}/qr` | 60 | 1 a second | workspace | 1 |
| Custom domain status: `GET /v1/domains` | 20 | 1 every 2 seconds | workspace | 5 |
| Custom domain changes: `POST /v1/domains` and `DELETE /v1/domains/{hostname}` together | 10 | 1 every 6 minutes | workspace | 360 |
| App Links checker: `GET /v1/tools/app-links` | 10 | 10 a minute | IP address | 60 |
| Live tap trace: `GET /v1/trace/{st}` | 30 | 1 a second | IP address | not sent |
| Link reports: `POST /v1/report` | 5 | 5 an hour | IP address | 3600 |
| Deep health check: `GET /healthz/deep` | 20 | 1 every 3 seconds | IP address | 60 |
| Hosted MCP server: `POST /mcp` | 60 | 1 a second | secret key | 1 |
| Hosted MCP server, wrong or missing keys | 20 | 20 a minute | IP address | 60 |

PNG QR codes also share one rendering budget across all workspaces (bigger images cost more of it). When it's busy you get `429` with `Retry-After: 2`: try again shortly, or ask for `format=svg`, which doesn't use that budget.

An SDK makes one or two of the app calls per link open, so one phone stays far below 60. People behind one office or mobile-carrier network share an IP address, so their calls count together.

## Size limits

| What | Limit | Answer when over |
|---|---|---|
| Links in one `POST /v1/links/batch` | 500 | `400` |
| Request body for `POST /v1/links/batch` | 2 MB | `413` |
| Request body for `POST /mcp` | 2 MB | `413` |
| Request body for every other public endpoint | 100 KB | `413` |
| Page size (`limit`) for `GET /v1/links` | 1 to 100; more is treated as 100 | `400` below 1 |
| Page size (`limit`) for webhook deliveries | 1 to 100 | `400` |

## New workspaces

For its first 24 hours, a new workspace can create **25 links**, and its links can be opened **500 times**. This stops throwaway accounts from sending spam through short links; the limit lifts by itself after the first day.

- Creating a link past the cap answers `429` with `{ "error": "new_account_limit", "reason": "links", "message", "limit", "resetsAt" }` and a `Retry-After` header.
- A tap past the cap shows the person a "taking a break" page instead of the destination, with `429`. Link previews in chat apps still work.

## Plan limits

Plan limits are monthly, not per second: new links a month (Free), taps a month, and the number of webhooks. Going over answers `402` with a `reason` (`links`, `clicks` or `webhooks`) and an `upgradeUrl`. Links you already shared keep working. Each plan's numbers are on [Pricing](https://straitlink.in/pricing/).

## Related

- [Error reference](https://straitlink.in/docs/errors/): every error the API can send.
- [Batch create](https://straitlink.in/docs/api/#batch-create): up to 500 links in one call, for imports.
