Guide
Limits at a glance
| Scope | Rate limit | Burst |
|---|---|---|
| Stage-wide (every authenticated route) | 100 req/sec | 200 |
GET /v1/p/{userId} (open-tracking pixel) | 50 req/sec | 100 |
The pixel route is capped to half the stage budget on its own so a flood of recipient image-fetches can’t starve the dashboard’s authenticated routes. This is by design (ADR 026).
429 response
When you exceed the limit, API Gateway short-circuits with 429 TOO_MANY_REQUESTS:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 30
X-Correlation-Id: 00000000-0000-4000-8000-000000000005
{
"error": {
"code": "TOO_MANY_REQUESTS",
"message": "Rate limit exceeded — retry after 30 seconds"
}
}
The Retry-After header is in seconds. API Gateway sets a default of 30s if
the limit was hit by burst exhaustion.
Recommended client behavior
Always honor Retry-After. Don’t hard-code your own backoff if the server
told you how long to wait.
async function callWithBackoff(url: string, init: RequestInit, attempt = 0): Promise<Response> {
const res = await fetch(url, init);
if (res.status !== 429 || attempt >= 5) return res;
const retryAfter = Number(res.headers.get('retry-after') ?? Math.min(30, 2 ** attempt));
// Add jitter to avoid thundering herd on the next bucket boundary.
const jitter = Math.random() * 500;
await new Promise((r) => setTimeout(r, retryAfter * 1000 + jitter));
return callWithBackoff(url, init, attempt + 1);
}
Patterns to apply:
- Exponential backoff with jitter — even when
Retry-Afteris present, jitter the wakeup so concurrent clients don’t all retry at the same millisecond. - Cap retries. 5 attempts is plenty; if you’re still 429ing after 5, you’ve got a structural problem (queue-size mismatch, runaway loop).
- Surface to the user. A 429 after 5 retries means your app is overloaded — back off the calling pattern, not just the HTTP retry.
Per-tenant limits (roadmap)
Today’s limits are stage-global — every customer shares the same 100 RPS budget. Two future levers:
- API key usage plans — already declared on the API Gateway side
(
esigkit-default-{stage}usage plan with 100k req/month quota); we haven’t wired per-customer keys to per-tenant quotas yet. - Per-org throttling — bigger lift, requires per-tenant quota on API Gateway. Tracked in the master plan.
If your use case will hit these caps, contact support — we’ll lift the cap on a per-customer basis until the per-tenant flow ships.