Rate limits
Token-bucket rate limiting on every /v1/* endpoint, with three layered scopes: per-key, per-tenant, and per-endpoint.
Tiers
| Tier | Per-key limit | Default for |
|---|---|---|
| free | 60 req/min | (reserved) |
| standard | 100 req/min | Solo, Duo, Crew plans |
| pro | 500 req/min | Studio, Empire plans |
| custom | Per-key override | Contact support for enterprise needs |
Layered scopes
- Per key. Each API key gets the tier rate above.
- Per tenant. All keys in a tenant combined are capped at 1000 req/min.
- Per endpoint. Expensive routes have tighter limits — e.g.
POST /v1/appointmentsis capped at 30/min/key,POST /v1/forms/:id/submissionsat 60/min/key.
The most-restrictive scope wins. A 429 response identifies which scope tripped via the X-RateLimit-Scope header (key, tenant, or endpoint).
Headers on every response
RateLimit-Limit: 100
RateLimit-Remaining: 97
RateLimit-Reset: 42(IETF draft format.) When you hit the limit:
HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 35
Retry-After: 35
X-RateLimit-Scope: key
Content-Type: application/problem+json
{
"type": "/errors/rate_limit_exceeded",
"title": "Rate limit exceeded",
"status": 429,
"detail": "The key-level rate limit of 100 requests per minute was exceeded. Retry in 35s.",
"code": "rate_limit_exceeded",
"extra": { "scope": "key", "limit": 100, "retry_after_seconds": 35 }
}Backoff
Respect Retry-After. If you don't, you'll keep getting 429s and burn your quota.
async function callWithBackoff(fn, maxAttempts = 5) {
for (let i = 0; i < maxAttempts; i++) {
const res = await fn();
if (res.status !== 429) return res;
const retryAfter = Number(res.headers.get('retry-after')) || (2 ** i);
await new Promise(r => setTimeout(r, retryAfter * 1000));
}
throw new Error('Rate-limited after max attempts');
}Bursting
The bucket allows natural bursts up to its capacity. A standard-tier key can burst ~100 requests instantly, then refill at ~1.67 req/sec. Plan around the long-run average, not the instantaneous limit.