Default tiers
Each team gets the following bursting capacity. The bucket refills at the sustained rate. Limits may change. We raise limits without notice — a loosening never breaks your integration — and announce any reduction in advance in the changelog. Always treat theX-RateLimit-Limit response header as the authoritative current value; don’t hard-code the numbers below into retry logic.
X-RateLimit-Limit reports the burst capacity — the “Capacity (burst)” column below, not the per-minute figure. The “Refill (sustained)” column is the rate the bucket refills at, and it is not carried in any header. So on the POST /v1/images class you will see X-RateLimit-Limit: 120 even though the sustained rate is 60/minute. Compare X-RateLimit-Remaining against the header, and size a steady-state workload against the refill column.
Character synthesis is limited below the image endpoint because each create, resynthesize or build fans out to up to eight renders internally. At 15 creates/minute that is 120 image generations/minute — which is why synthesis has its own bucket rather than drawing on the
POST /v1/images budget. The five synthesis routes share one bucket (see the note under Implementation notes below).
Reference uploads get a bucket of their own. PUT /v1/characters/{id}/refs/{view} costs no credits, but every call runs a visual check on the bytes you sent, so its ceiling is stated explicitly instead of being hidden inside the cheap-writes budget.
Headers on every response
X-RateLimit-Limitis the bucket capacity (burst) for this endpoint class — not the sustained per-minute rate. See the note above.X-RateLimit-Remainingis the tokens left after this request.X-RateLimit-Resetis the epoch second at which the bucket would be back to full, assuming no further requests.
When you hit the limit
Retry-After seconds and retry. Don’t hammer — the bucket only refills at the sustained rate.
The message names the endpoint class you exceeded — for example characters_synthesize rather than the images_post shown above. Switch on error.code (always too_many_requests), never on the human-readable message string.
Implementation notes
- Limits are per-team, keyed off the API key’s
team_id. Multiple keys on the same team share the bucket. - Bucket counts are atomic across concurrent requests on the same team — no race window between read and write.
- Each endpoint class has its own bucket, so heavy POST traffic doesn’t starve your reads.
POST /v1/images/:id/cancelis bucketed underimages_postregardless of whether the canceled generation isimg_*orvid_*(single shared bucket on the cancel route — a pure-video team’s cancel storm would still draw fromimages_post). Runaway cancels can’t drain the GET read bucket.- Character synthesis shares one per-team bucket across five routes:
POST /v1/characters,POST /v1/characters/{id}/refs/regenerate,POST /v1/characters/{id}/refs/{view}/regenerate,POST /v1/characters/{id}/resynthesize, andPOST /v1/characters/{id}/build. A burst of regenerations, resynthesizes or builds draws down the same 30-token bucket as new creates — they are not independently budgeted. If you batch-onboard characters while also regenerating poses, budget both against the single 15/minute sustained rate. Note thatregeneratemints a single view but still draws one full token from this bucket (sized for eight-view creates), so a regenerate-heavy workload is capped at 15/minute even though it produces fewer images; abuildthat renders two missing views costs exactly the same one token. (X-RateLimit-Remainingreflects consumption across all five routes.) - Filing reference photos (
PUT /v1/characters/{id}/refs/{view}) draws oncharacters_ref_upload, not on the cheap-writes bucket and not on synthesis. Removing a view (DELETE /v1/characters/{id}/refs/{view}) and creating a draft (POST /v1/characters/drafts) are cheap writes and draw oncharacters_post. So an upload loop cannot starve your draft bookkeeping, and neither can starve a build.

