Rate limits
Request ceilings, and how to back off cleanly when you hit one.
On this page
Limits are per endpoint and per identity, and more than one can apply to a
single request. Exceeding any of them returns 429 with RATE_LIMITED and a
Retry-After header.
"Per identity" means per user, on the credential you authenticated with. Two people on the same team have separate budgets; two tokens belonging to the same person share one.
Generation
| Endpoint | Limit |
|---|---|
| Create generation | 10 per minute and 100 per hour |
| Preview cost | 60 per minute |
| Get run status | 60 per minute, and one poll per 2 seconds for any single request_id |
Both create limits apply at once, so a burst of 10 is fine but sustaining it is
not. The per-request status limit is separate from the per-minute one: polling
one run every second trips it even when you are well inside 60 per minute. Honor
retry_after_seconds from the create response before your first poll, and
prefer a webhook over polling in production.
Tools
| Endpoint | Limit |
|---|---|
| List tools, Get a tool | 120 per minute |
Tool tags
| Endpoint | Limit |
|---|---|
| List tool tags, Get a tool tag | 120 per minute |
Library
| Endpoint | Limit |
|---|---|
| List generations, Get a generation | 60 per minute |
| Delete a generation | 30 per minute |
Boards
| Endpoint | Limit |
|---|---|
| Reads: list, get | 60 per minute |
| Writes: create, update, delete, access, and all node edits | 30 per minute |
Reads and writes are counted separately, so listing boards does not consume the budget for editing them.
Uploads
| Endpoint | Limit |
|---|---|
| List uploads, Get an upload, Delete an upload | 60 per minute |
| Create an upload | 100 per hour |
Identity
| Endpoint | Limit |
|---|---|
| Get the current user | 60 per minute |
/me does more work than its size suggests: it reads your profile and every
account you belong to. Call it when a session starts or an account changes,
not before every request.
Authentication
| Endpoint | Limit |
|---|---|
| Start the device flow | 5 per minute, per IP |
| Complete the device flow | 10 per minute |
| Poll the device flow | No fixed ceiling. Poll at the interval the start response gave you. Polling faster returns SLOW_DOWN, and the fix is to double your interval rather than to retry. |
| Refresh a token | 20 per minute, per IP |
| Device management | 60 per minute |
The two per-IP limits are the only ones not counted per identity, because at that point in the flow there is no identity yet.
Reporting
| Window | Maximum requests |
|---|---|
| Per minute | 20 |
| Per hour | 120 |
Both apply simultaneously, so 20 requests in the first minute of an hour is fine, but sustaining that rate is not. These are counted per company rather than per user, because a Company API Access Token names a company.
Reporting also runs one request at a time per company. A second request that
starts while the first is still running is rejected with RATE_LIMITED, so run
your exports in sequence rather than in parallel.
At the maximum page size of 100,000 rows, the hourly limit still covers 12 million rows per hour. If you are approaching that, request larger pages rather than more of them.
Handling a 429
Back off exponentially, and add jitter so that a fleet of clients retrying together does not synchronize into a second spike.
async function withRetry(request, { attempts = 5 } = {}) {
for (let attempt = 0; attempt < attempts; attempt++) {
const response = await request();
if (response.status !== 429) return response;
// Exponential backoff with jitter: 1s, 2s, 4s, 8s, plus up to 1s of noise.
const base = 2 ** attempt * 1000;
const wait = base + Math.random() * 1000;
await new Promise(resolve => setTimeout(resolve, wait));
}
throw new Error('Still rate limited after retrying.');
}import random
import time
def with_retry(request, attempts=5):
for attempt in range(attempts):
response = request()
if response.status_code != 429:
return response
# Exponential backoff with jitter: 1s, 2s, 4s, 8s, plus up to 1s.
time.sleep(2**attempt + random.random())
raise RuntimeError("Still rate limited after retrying.")Staying well under the limits
- Request larger pages. One request for 50,000 rows costs the same against your budget as one for 50.
- Page forward, do not re-query. Following
next_cursorcosts one request per page. Re-running the same window from the start to pick up new rows costs the whole export again. - Spread scheduled jobs. A nightly export that starts exactly on the hour competes with everything else that does the same.
