Rate limits
Requests are counted per key, not per IP address, so two teams behind one office connection don't share a budget. There are two budgets, each over a rolling 60 seconds:
| Budget | Per key | What spends it |
|---|---|---|
| Reads | 120 a minute | Every GET except export: listing packs and assets, downloading an asset file, polling a job |
| Writes | 30 a minute | Every other request (creating a pack, generating, uploading, importing, deleting) and pack export |
Unused requests don't carry over to the next minute.
Export is the one GET that counts as a write, because it builds the pack's
.zip on demand.
The MCP server counts its tool calls against the same two budgets (a tool that reads spends a read, one that changes something spends a write). The origin check endpoints are limited separately, per address.
Being refused#
A request over budget answers 429 with the usual envelope and a
Retry-After header in whole seconds:
{
"error": {
"code": "rate_limited",
"message": "Too many requests on this API key. Try again in 24s."
}
}Wait out Retry-After and carry on. Retrying immediately and repeatedly only
gets more refusals until the window rolls over.
Polling inside the read budget#
A job polled every two seconds is 30 reads a minute, so 120 covers four generations in flight at once. For more than that, poll less often: a model takes minutes, and checking every ten seconds loses nothing over checking every two. See Jobs and polling.
Credits are still the real limit#
Rate limits stop a runaway loop; they don't limit spend. A generation you can't
afford is refused with 402 insufficient_credits however slowly you ask, and
a generation costs the same whenever you make it.
If you're hitting these
Both numbers are well above what a working integration needs. Hitting one usually means a retry loop with no backoff, or a poll on a timer rather than after the previous response. If your use genuinely needs more, tell us in Discord.