Rate limits
A per-plan per-workspace budget, a per-IP flood guard, the rate-limit headers on every response, stricter export and crawl caps, and a sane backoff loop.
v1 enforces two rate limits. They are independent, and either can reject a request.
| Limit | Window | Scope |
|---|---|---|
| Per-plan (table below) | per minute | Per workspace, checked once the key resolves |
| 300 requests | per minute | Per IP, checked before the key is looked up |
The per-IP limit runs first, deliberately: it bounds floods of missing or invalid keys, which would otherwise reach a database lookup unthrottled. It sits at the highest plan tier (300/min), so it never caps a Business integration calling from one server IP below that workspace's own budget. The per-workspace limit is what bounds one valid key sprayed across many machines.
The per-workspace budget is per plan
Your workspace's per-minute budget follows its plan. A resold (reseller client) workspace carries its own client plan's tier.
| Plan | Requests / minute |
|---|---|
| Free | 10 |
| Starter | 30 |
| Pro | 60 |
| Business | 300 |
So five servers sharing one Pro key are capped at 60 requests per minute in total; upgrading the plan raises the budget.
Stricter per-endpoint limits
A few endpoints carry their own limit on top of the global two, because each request is expensive:
| Endpoint | Limit | Scope of the limit |
|---|---|---|
GET /conversations/export (CSV) | 10 / minute | per workspace |
GET /leads/export (CSV) | 10 / minute | per workspace |
POST …/sources/crawl (website crawl) | 10 / minute | per workspace |
POST /analytics/topics (LLM clustering) | 10 / minute | per workspace |
PUT/POST /reseller/custom-domain | 30 / minute | per IP |
POST /reseller/mail-config/test | 5 / hour | per organization |
POST /reseller/clients (create a client) | 20 / hour | per organization |
POST /members/invite | 20 / hour | per workspace |
All of them return the same 429 as the global limits.
The response
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"subcode": "rate_limited",
"message": "Rate limit exceeded."
}
}HTTP status 429. The per-workspace limit carries subcode: "rate_limited"; see Errors.
Rate-limit headers
Every response that got far enough to resolve a key — a success, a 429, or the access-gate 403 — carries three headers:
| Header | Value |
|---|---|
X-RateLimit-Limit | Your plan's per-minute budget |
X-RateLimit-Remaining | Requests left in the current window |
X-RateLimit-Reset | When the window resets, as a unix epoch time in seconds |
A 429 also carries Retry-After — the number of seconds to wait. On the per-workspace limit it is the time left in the window; on the per-IP flood guard (which rejects before a key resolves, so it reports no budget headers) it is the full window length, 60.
Prefer these headers to guessing: read Retry-After on a 429, and watch X-RateLimit-Remaining to slow down before you hit the wall.
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 resolve themselves. See Errors.
Staying under the limit
- Use the maximum
limitof 200 when paging, so a given dataset costs fewer requests. 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