Rate Limits
Per-key request limits, rate-limit response headers, and how to back off cleanly.
Stile rate-limits API requests per key in fixed one-minute windows. The limits are generous for normal traffic and exist to bound the blast radius of a leaked key or a runaway retry loop.
Limits
Limits are per key, by key type:
| Key type | Limit |
|---|---|
Secret (stile_sk_) | 1,000 requests / min |
Publishable (stile_pk_) | 100 requests / min |
Publishable keys get a tighter budget because they're exposed in the browser. Limits are per key, not per organization — a separate key for each service gets its own budget.
Rate-limit headers
Authenticated requests processed by the rate limiter include the current state of the window, so you can throttle proactively instead of waiting for a 429:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 997
X-RateLimit-Reset: 1741564920| Header | Meaning |
|---|---|
X-RateLimit-Limit | Your ceiling for the current window. |
X-RateLimit-Remaining | Requests left before you're limited. |
X-RateLimit-Reset | Unix timestamp (seconds) when the window resets. |
When you're limited
Exceeding the limit returns 429 Too Many Requests with code rate_limit_exceeded and a Retry-After header (seconds to wait). Honor it:
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get("Retry-After") || "1", 10);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
// retry the request
}Backend-agnostic retry behavior
Honor Retry-After for 429 responses. For network failures and temporary 5xx responses, use
bounded exponential backoff with jitter. Reuse the same Idempotency-Key when retrying writes.
A separate limit protects returning-user lookups
accept_existing is additionally rate-limited to 10 attempts per email per 15-minute window. The
eleventh attempt returns 429 accept_existing_rate_limited to prevent credential enumeration. See
Returning Users.