API reference / Rate limits & quotas

Rate limits & quotas

Two independent limits apply to /v1/generate: a monthly quota (how many documents you can generate per calendar month) and a per-minute rate limit (how fast you can send requests). They exist for different reasons and return different errors.

Limits by plan

PlanMonthly quotaRequests / min
Free505
Starter1,00030
Pro5,000120
Scale25,000300

(Plans also differ on active API keys and history retention. See Plans & limits.)

Monthly quota: limit_exceeded (429)

Each plan has a monthly generation quota, counted per calendar month in UTC (a period looks like 2026-06). When you reach it, further calls return limit_exceeded (429) until the next month begins. The fix is to upgrade or wait for the reset.

Only successful generations count toward the quota. A failed call never does.

Request rate: rate_limited (429)

Each document is a full browser render, so there is also a short-term cap on how many requests per minute you may send. Bursting past it returns rate_limited (429) with a Retry-After header telling you how long to wait. This is "slow down", not "you're out of quota".

Both limits use HTTP 429, but the code distinguishes them. rate_limited means wait a moment and retry. limit_exceeded means you've hit your monthly allowance. Always branch on code, not the status alone.

Capacity: service_unavailable (503)

If the render engine is momentarily saturated, a request can be shed with service_unavailable (503) rather than queued indefinitely. This is transient: retry after a short, backed-off delay, honoring the Retry-After header when the response carries one.

Accounting guarantee

The successful generation and its usage increment are written in a single transaction, so your usage figure can never disagree with what actually rendered, and a failed render leaves the counter untouched.

Retrying safely

A failed call never produces a document or consumes quota, so retrying a failure is always safe. The one genuinely ambiguous case is a lost response: the request may have succeeded (and billed) even though you never saw the bytes. That's what the Idempotency-Key header is for: retry with the same key and you get the first outcome back, billed exactly once. So:

  • Retry the transient failures with exponential backoff: rate_limited (wait at least the Retry-After value), service_unavailable, and usually internal_error.
  • Retry network failures only under an idempotency key: without one, a retry after a lost response can generate (and bill) a second document.
  • Don't retry request errors (invalid_json, invalid_data, template_not_found, template_not_published): fix the request first, or you'll just fail again.
  • Guard user-triggered generation against double-submits: an idempotency key derived from the user action makes the second click a free replay.

A reasonable policy: up to ~3 retries, starting around 1s and doubling, capped at a few seconds, and never retrying a 4xx that's your request's fault. The TypeScript SDK implements exactly this policy out of the box, including the key-gated network retries. See Errors for the per-code guidance.