Rate limits
Two independent 120-per-minute limits — one per IP, one per workspace — the 429 they return, and a backoff loop that behaves under load.
v1 enforces two rate limits. They are independent, and either can reject a request.
| Limit | Window | Scope |
|---|---|---|
| 120 requests | per minute | Per IP, checked before the key is looked up |
| 120 requests | per minute | Per workspace, checked once the key resolves |
The per-IP limit runs first, deliberately: it bounds floods of missing or invalid keys, which would otherwise reach a database lookup unthrottled. The per-workspace limit is what bounds one valid key sprayed across many machines.
So a single client from one address is effectively capped at 120 requests per minute; five servers sharing one key are still capped at 120 per minute in total.
The response
{
"success": false,
"error": { "code": "RATE_LIMITED", "message": "Rate limit exceeded." }
}HTTP status 429.
Backing off
Retry with exponential backoff and jitter, and cap the number of attempts.
async function getWithRetry(url, headers, attempts = 5) {
for (let attempt = 0; attempt < attempts; attempt += 1) {
const res = await fetch(url, { headers });
const body = await res.json();
if (body.success) return body.data;
if (body.error.code !== "RATE_LIMITED") {
throw new Error(`${body.error.code}: ${body.error.message}`);
}
// 1s, 2s, 4s, 8s … plus jitter, so parallel workers do not resynchronise.
const wait = 2 ** attempt * 1000 + Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, wait));
}
throw new Error("Still rate limited after retries.");
}Two things to avoid:
- Retrying immediately. The window is a minute; an instant retry just spends
another request.
- Retrying anything else.
UNAUTHORIZEDandFORBIDDENwill not resolvethemselves. See Errors.
Staying under the limit
- Use the maximum
limitof 200 when paging, so a given dataset costs fewerrequests. See Pagination.
- Sync on a schedule rather than polling in a loop. Nothing in v1 changes fast
enough to reward a tight poll.
- For "tell me when something happens", use
webhooks instead of polling — they push, and they cost you no requests.
Where to go next
Last updated