Rate limits
Per-key request limits, the 429 response with Retry-After, and how to back off correctly.
Requests are rate-limited per API key. Exceeding the limit returns
429 Too Many Requests with a Retry-After header telling you exactly how long
to wait.
The limit
| Limit | Value |
|---|---|
| Per-key request rate | 600 requests / minute (10 req/s sustained) |
| Window | Fixed one-minute window |
| Response when exceeded | 429 + Retry-After |
The limit is per key, so isolating agents or environments onto separate keys gives each its own budget.
Request rate limits are separate from plan limits on email volume and
storage — those are per-tenant and return a 403 cap_hit, not a 429. See
Errors.
The 429 response
When you go over, the request has no side effects — nothing is partially
performed — and the response is the standard error envelope plus a Retry-After
header:
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json
{
"name": "rate_limited",
"message": "Rate limit exceeded. Retry after 12 seconds."
}Retry-After is the number of seconds until the current window resets.
Backing off correctly
Honor Retry-After when it's present, and otherwise back off exponentially with
jitter. Cap the number of retries so a persistent 429 surfaces as an error
rather than hanging.
async function withRetry(request: () => Promise<Response>, maxRetries = 5) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await request();
if (res.status !== 429) return res;
// Prefer the server's Retry-After; fall back to exponential backoff.
const retryAfter = Number(res.headers.get("Retry-After"));
const backoff = Number.isFinite(retryAfter)
? retryAfter * 1000
: Math.min(2 ** attempt * 1000, 30_000) + Math.random() * 250;
await new Promise((r) => setTimeout(r, backoff));
}
throw new Error("Rate limited: retries exhausted");
}import random, time
def with_retry(request, max_retries=5):
for attempt in range(max_retries + 1):
res = request()
if res.status_code != 429:
return res
# Prefer the server's Retry-After; fall back to exponential backoff.
retry_after = res.headers.get("Retry-After")
if retry_after is not None:
backoff = int(retry_after)
else:
backoff = min(2 ** attempt, 30) + random.random() * 0.25
time.sleep(backoff)
raise RuntimeError("Rate limited: retries exhausted")The typed SDKs can do this for you — they expose the Retry-After value on the
rate-limit error and optionally auto-retry with backoff.
Pagination
Every list endpoint pages the same way — a cursor token, a consistent envelope, and newest-first ordering by default.
Create an API key POST
Create a named, organization-scoped API key. With no `permissions` the key has full access; supply a `permissions` object to restrict it to a whitelist (only flags set `true` are granted). The raw `secret` is returned **once** in this response and never again — store it now. A restricted key that tries to mint a child with more permissions than itself is forbidden, and the per-tenant key limit is enforced.