Rate limits & errors

Four enforcement levels, and how to stay under them.

Limits are enforced hierarchically — burst, then installation, then app, then platform. Test and live traffic are counted separately, so sandbox load can never eat live quota.

Per-installation quotas

Group Test Live
Read 120 req/min, burst 10/s 600 req/min, burst 30/s
Write 60 req/min, burst 5/s 300 req/min, burst 15/s
Heavy (analytics, exports) 20 req/min, burst 2/s 60 req/min, burst 5/s

App-wide ceilings

Across all installations of one app: 3 000 req/min in test, 30 000 req/min in live. A generous platform ceiling sits above that as an emergency brake.

Headers

Every response carries:

X-RateLimit-Limit: 600
X-RateLimit-Remaining: 583
X-RateLimit-Reset: 27
Retry-After: 27      # only on 429

Handling 429

async function call(url, token, attempt = 0) {
  const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
  if (res.status === 429 && attempt < 5) {
    const wait = Number(res.headers.get("Retry-After") ?? 1) * 1000;
    await new Promise((r) => setTimeout(r, wait + Math.random() * 250));
    return call(url, token, attempt + 1);
  }
  return res;
}

Add jitter, cap retries, and never retry a 403.

Staying efficient

  • React to webhooks instead of polling.
  • Cache /v1/merchant; it changes rarely.
  • Use time-window filters for backfills, and run them off-peak.
  • When usage crosses 80 % of an app quota we notify you before throttling bites.