Rate Limits
Per-IP and per-endpoint request limits, the headers that expose them, and how to retry safely
3 min readThe API enforces two layers of rate limiting: a global per-IP budget that protects every route,
and tighter windows on auth-critical endpoints like sign-in and two-factor verification. Both
layers return 429 Too Many Requests with a stable error code and standard headers you can read
on every response.
Global limit
A single IP may issue up to 500 requests per 15 minutes across all routes combined.
| Window | Max requests | Scope |
|---|---|---|
| 15 minutes | 500 | Per IP, global |
Per-endpoint auth limits
On top of the global budget, auth-critical endpoints have stricter windows to prevent credential stuffing and brute-force.
| Endpoint | Max attempts | Window |
|---|---|---|
/api/auth/sign-in/email | 5 | 60 seconds |
/api/auth/sign-up/email | 3 | 60 seconds |
/api/auth/two-factor/* | 5 | 60 seconds |
/api/auth/passkey/* | 10 | 60 seconds |
/api/auth/magic-link/* | 5 | 1 hour |
Hitting any of these returns 429 with code RATE_LIMIT_ERROR. Wait for the window to expire — there is no retry-with-backoff strategy that beats the limit.
Response headers
Every response carries standard rate-limit headers.
| Header | Description |
|---|---|
RateLimit-Limit | Maximum requests permitted in the current window |
RateLimit-Remaining | Requests remaining in the current window |
RateLimit-Reset | Seconds until the window resets |
Retry-After | Present on 429 responses — seconds to wait before retrying |
HTTP/1.1 200 OK
RateLimit-Limit: 500
RateLimit-Remaining: 482
RateLimit-Reset: 612
Content-Type: application/json; charset=UTF-8
{
"success": true,
"status": 200,
"code": "OK",
"message": "Projects fetched successfully",
"data": [...]
}429 response
When you exceed the global limit, the API returns:
{
"success": false,
"status": 429,
"code": "RATE_LIMIT_ERROR",
"message": "Too many requests from this IP, please try again later.",
"meta": {
"message": "Too many requests from this IP, please try again later."
}
}The accompanying Retry-After header tells you how many seconds to wait. Honour it.
Retry with backoff
Use exponential backoff for 429 and transient 5xx responses. Read Retry-After first; fall back to 2^attempt seconds if the header is missing.
async function requestWithBackoff(url, options, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fetch(url, options);
if (res.status !== 429 && res.status < 500) return res;
if (attempt === maxRetries) return res;
const retryAfter = Number(res.headers.get('Retry-After')) || Math.pow(2, attempt);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
}
}import time, requests
def request_with_backoff(method, url, max_retries=3, **kwargs):
for attempt in range(max_retries + 1):
res = requests.request(method, url, **kwargs)
if res.status_code != 429 and res.status_code < 500:
return res
if attempt == max_retries:
return res
retry_after = int(res.headers.get('Retry-After', pow(2, attempt)))
time.sleep(retry_after)Staying under the limit
- Cache reads: Cache responses whose underlying data changes slowly (reference data, organization settings, user profile).
- Batch writes when possible: Use endpoints that accept arrays rather than calling a single-resource endpoint in a loop.
- Poll sparingly: For long-running work, poll every few seconds rather than every few milliseconds.
- Use webhooks when available: Subscribing to provider webhooks is far cheaper than polling for state changes.
- Observe your budget: Log
RateLimit-Remainingon hot paths; a low floor is a signal to back off preemptively.
Need higher limits?
If your integration legitimately needs more headroom, contact your account manager with details about your use case and traffic pattern.
Best Practices
- Read
Retry-Afterbefore guessing a delay: The server knows exactly when the window resets. Your backoff math is an approximation. - Never retry sign-in or 2FA on
429: These use credential-safety limits. Retrying only extends the lockout. - Log
code === 'RATE_LIMIT_ERROR'separately: Distinguish rate limits from generic4xxso you can spot noisy callers. - Distribute traffic across IPs where legitimate: The limiter is per-IP. A single bot from one IP will hit the cap long before a fleet of workers across a NAT pool.
- Do not rotate IPs to dodge the limit: That is treated as abuse and can get the account suspended.
Next Steps
- Handle errors: Review Error Handling for the full list of codes including
RATE_LIMIT_ERROR. - Scope down: Confirm your Scopes & Permissions before you ship anything that runs in a tight loop.
- Plan for SSO: If you administer an organization, set up Enterprise SSO.
- Explore endpoints: Jump into the API Reference.
Related Pages
Introduction
How the API is structured, how authentication works, and how multi-tenant requests are scoped
Quick Start
Sign in, issue a Personal Access Token, and make your first authenticated call
API Structure
Request headers, response envelope, pagination, and the query conventions shared by every endpoint
Error Handling
Error envelope, full code list, and the retry strategies that actually work