Skip to content

Rate Limits

The User API enforces per-credential rate limits to keep the platform stable and fair. Limits are applied per authenticated principal (the identity behind your token) — not per IP address — and scale with your workspace's plan.

Limits by plan

Every request against a /v1 endpoint counts toward a rolling 60-second window. When you exceed your limit, further requests in that window are rejected with 429 Too Many Requests until the window resets.

Plan Requests per minute
Free 10
Pro 100
Elite 500
Enterprise 1,000

Limits are per principal

The limit is keyed on the identity that owns the credential, so all tokens acting as the same principal share one budget. Using multiple Personal Access Tokens does not multiply your limit.

Rate-limit headers

Every response includes headers describing your current budget, so you can throttle proactively instead of waiting for a 429:

Header Description
X-RateLimit-Limit Maximum requests allowed in the current window (your plan limit).
X-RateLimit-Remaining Requests remaining in the current window.
X-RateLimit-Reset Unix timestamp (seconds since epoch) when the window resets.

When a request is throttled, the 429 response additionally includes:

Header Description
Retry-After Seconds to wait before retrying. This is the actual time to reset, not the full window, so honoring it waits only as long as necessary (minimum 1).

The 429 response

A throttled request returns HTTP 429 with this body:

{
  "code": "rate_limit_exceeded",
  "message": "Rate limit exceeded. Please try again later.",
  "request_id": "abc123xyz"
}

Match on the machine-readable code (rate_limit_exceeded), not the human-readable message.

Distinguish from the events stream cap

The Server-Sent Events endpoint enforces a separate concurrent connection cap that also returns 429, but with code = too_many_requests. See User Events (SSE).

Handling throttling

The recommended pattern is to respect Retry-After and back off exponentially on repeated 429s:

import time
import requests

def request_with_backoff(session, method, url, **kwargs):
    for attempt in range(5):
        resp = session.request(method, url, **kwargs)
        if resp.status_code != 429:
            return resp
        # Prefer the server's hint; fall back to exponential backoff.
        retry_after = int(resp.headers.get("Retry-After", 2 ** attempt))
        time.sleep(retry_after)
    return resp  # last response after exhausting retries
async function requestWithBackoff(url: string, init: RequestInit): Promise<Response> {
  for (let attempt = 0; attempt < 5; attempt++) {
    const resp = await fetch(url, init);
    if (resp.status !== 429) return resp;
    const retryAfter = Number(resp.headers.get("Retry-After") ?? 2 ** attempt);
    await new Promise((r) => setTimeout(r, retryAfter * 1000));
  }
  return fetch(url, init);
}

Best practices

  • Read the headers. Watch X-RateLimit-Remaining and slow down before you hit zero.
  • Honor Retry-After. It tells you exactly when the window resets.
  • Batch and paginate efficiently. Request larger pages (up to the pagination maximum) instead of many small requests.
  • Prefer webhooks and events over polling. Workspace Webhooks and User Events (SSE) push changes to you, eliminating poll traffic entirely.

Next steps