Core concepts

Rate limits

Two budgets run at once: a per-minute burst and a per-day total. Both are scoped to your account and set by your plan.

Limits by plan

PlanRequests per minuteRequests per day
Free605,000
Starter9012,000
Pro12025,000
Crew24060,000
Company600150,000
Enterprise (all plans)1,200500,000

The daily budget resets at 00:00 UTC. The burst window is one minute. Live and test keys are metered separately, so exercising the API with a test key does not eat into your production budget.

Any plan not listed — including a new one we add later — falls back to 60 requests per minute and 5,000 per day.

These are abuse rails, not your usage allowance

Rate limits exist to stop runaway loops and abuse. What you can actually do with the product is governed by your plan’s entitlements, not by these numbers. If a legitimate integration needs more headroom, talk to us — limits can be raised per account.

Reading your remaining budget

Every response tells you exactly where you stand:

http
HTTP/1.1 200 OK
X-Request-Id: req_b07b069f0a83492877e3fa31
X-RateLimit-Limit-Burst: 240
X-RateLimit-Remaining-Burst: 237
X-RateLimit-Limit-Daily: 60000
X-RateLimit-Remaining-Daily: 59781
X-RateLimit-Reset: 1785765300
HeaderSent onMeaning
X-RateLimit-Limit-Burstevery responseYour plan's ceiling for the current one-minute window.
X-RateLimit-Remaining-Burstevery responseRequests left in the current one-minute window.
X-RateLimit-Limit-Dailyevery responseYour plan's ceiling for the current UTC day.
X-RateLimit-Remaining-Dailyevery responseRequests left today (resets at 00:00 UTC).
X-RateLimit-Resetevery responseUnix seconds at which the burst window rolls over.
Retry-After429 onlySeconds to wait before retrying. Never more than 60 for a burst rejection.

When you go over

You get a 429 with the error type rate_limit_error and a Retry-After header. The message names which budget ran out, and X-RateLimit-Remaining-Daily tells you whether waiting a minute will help or whether you are done for the day.

Bursting does not burn your daily budget

Requests rejected by the per-minute limit do not count against your daily total. Hitting the burst ceiling costs you nothing but the wait — so a client that backs off correctly is never penalised for retrying.

Handling a 429

javascript
async function callWithRetry(url, init, attempt = 0) {
  const res = await fetch(url, init);
  if (res.status !== 429 || attempt >= 5) return res;

  // Retry-After is authoritative. Add a little jitter so a fleet of
  // workers does not all wake up in the same millisecond.
  const wait = Number(res.headers.get("Retry-After") ?? 1);
  const jitter = Math.random() * 250;
  await new Promise((r) => setTimeout(r, wait * 1000 + jitter));

  return callWithRetry(url, init, attempt + 1);
}
  • Honour Retry-After rather than guessing. For a burst rejection it is never more than 60 seconds.
  • Add jitter. Without it, every worker that got a 429 at the same moment retries at the same moment.
  • Cap your retries. If the daily budget is exhausted, backing off will not fix it before midnight UTC.
  • Watch X-RateLimit-Remaining-Daily and slow down before you hit zero, rather than sprinting into the wall.

A separate limit applies before we read your key

There is also a per-IP limit applied ahead of authentication, which returns 429 with the code ip_throttled. It is an abuse rail against unauthenticated floods and sits well above the per-account limits, so a normal integration will never meet it — but if you route a very large number of clients through one IP, that is what you would be seeing.

Rate limits · XOsign API docs