Esc

↑↓ move↵ openIndex · Pagefind
API preview · design draft
Rate limits

Generous, predictable, and in the headers.

Limits are per key and reported on every response, so your sync can pace itself instead of guessing.

API preview · design draftDraft numbers · subject to change

Draft limits

These are starting proposals, not commitments. If a nightly job-cost sync or a month-end pay app run would hit them, tell us. That's exactly what early access is for.

  • Reads
    100 requests / second per key
    GET on any resource
  • Writes
    25 requests / second per key
    POST, PATCH, DELETE
  • List page size
    Up to 100 objects
    Use cursors, not offsets
  • Concurrent requests
    20 in flight per key
    Queue the rest client-side
  • Webhook endpoints
    16 per account
    Each can subscribe to any events

Headers

Every response includes RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds). A 429 adds Retry-After.

429 response
HTTP/2 429
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 4
Retry-After: 4
Content-Type: application/json

{
  "error": {
    "type": "rate_limited",
    "message": "Too many requests. Retry after 4 seconds.",
    "request_id": "req_Z1q9d"
  }
}

Back off, then retry

On 429 or 5xx, wait for Retry-After (or exponential backoff with jitter) and try again. Reuse the same Idempotency-Key so a retried write never posts twice.

Bulk work? Prefer webhooks over polling, and expand[] over N+1 fetches. One change order with its RFI and pay app is one request.

Retry helper
async function call(url, init, tries = 5) {
  for (let i = 0; i < tries; i++) {
    const res = await fetch(url, init);
    if (res.status !== 429 && res.status < 500) return res;
    const wait = Number(res.headers.get("Retry-After")) || 2 ** i;
    await new Promise(r => setTimeout(r, (wait + Math.random()) * 1000));
  }
  throw new Error("gave up after retries");
}
// Same Idempotency-Key on every retry: a write lands once.