Rate limits
Two limits apply to every request, one after the other. Both answer 429, and the id in the body tells you which one you hit.
| # | Limit | Applies to | Budget | id |
|---|---|---|---|---|
| 1 | Global ceiling | Every endpoint | 10 requests/second | rate_limit_exceeded |
| 2 | Endpoint budget | Endpoints in a limited tier | See below | request_rate_limited |
1. Global ceiling
10 requests per second, across every endpoint. Checked first, before anything below. It is a burst guard: sustain 10 requests a second and you will exhaust most endpoint budgets within the minute anyway, so in practice it is the endpoint budget you meet first.
2. Endpoint budgets
Every endpoint belongs to one tier, and the tier sets the budget. Each tier is counted separately, so writes never eat into your read budget.
Each budget is enforced over two windows at once — per minute and per hour — and both must pass.
| Tier | Per minute | Per hour |
|---|---|---|
| Polling | 600 | 18 000 |
| Read | 120 | 3 000 |
| Validation | 120 | 2 400 |
| Write | 60 | 1 200 |
| No endpoint budget | — | — |
Polling
600/min · 18 000/hour. Called in a loop until a job finishes.
| Endpoint | Guide |
|---|---|
GET /designs/import/json/{importId} | Import Status |
GET /design-duplication-requests/{duplicateRequestId} | Use a Workspace Template |
GET /generation-request/{generationRequestId} | Asynchronous Generation |
Read
120/min · 3 000/hour. Listings and single-resource reads.
| Endpoint | Guide |
|---|---|
GET /designs | List Designs |
GET /designs/{designId} | Design Details |
GET /designs/{designId}/formats/{formatSpecifier} | Design Format Details |
GET /designs/import/json | List your imports |
GET /banners/{bannerId} | Asynchronous Generation |
GET /fonts | Fonts |
GET /projects | Projects |
GET /workspace-templates | Workspace Templates |
GET /workspace-template-categories | Workspace Templates |
POST /auth | Authentication |
Validation
120/min · 2 400/hour. Dry runs, which create nothing.
| Endpoint | Guide |
|---|---|
POST /designs/import/json?validate_only=true | Design Import — validate_only |
Write
60/min · 1 200/hour. Anything that creates, changes or exports.
| Endpoint | Guide |
|---|---|
POST /designs/import/json | Create Import |
PUT /designs/import/json/{importId} | Validate Import |
GET /designs/{designId}/as-import | Export a Design |
POST /workspace-templates/{companyTemplateId}/use | Use a Workspace Template |
POST /async/banners/export | Asset Export |
POST /projects | Projects |
POST /designs/{designId}/dynamic-image-url | Create a Dynamic Image |
No endpoint budget
Generation endpoints are in no tier, so nothing on this page bounds them per minute or per hour. They are not unlimited: the global ceiling of 10 requests/second still applies, and your plan's credits gate them on top.
Polling the resulting job is budgeted — see Polling.
| Endpoint | Guide |
|---|---|
POST /banner-builder/{designId}/generate | Generate Single Image |
POST /async/banner-builder/{designId}/generate | Asynchronous Generation |
POST /async/banner-builder/{designId}/generate-multipage-pdf | Multi-Page PDF |
Why the tiers differ
Write is the tightest: one call is several database writes, two or more storage uploads, a queue publish and one outbound fetch per distinct layer src. Count round trips, not calls — exporting a design and importing it back spends two write calls, so 1 200/hour is 600 export–edit–import cycles.
Validation is looser than write so that iterating on a payload until it validates never costs you an import. Cheap, but not free: it still decodes every data URI you send.
Polling is the loosest because those endpoints are meant to be called in a loop. An import's status response tells you when to come back (next_check_after_ms, 2 seconds while processing), which is 30 requests a minute for one job. Twenty concurrent jobs at that cadence still fit — honour next_check_after_ms and you will not meet the limit.
Headers
| Header | Sent on |
|---|---|
X-RateLimit-Limit | every response from a budgeted endpoint |
X-RateLimit-Remaining | every response from a budgeted endpoint |
X-RateLimit-Reset | every response from a budgeted endpoint |
Retry-After | a 429 only, and never 0 |
All four describe whichever of the two windows you are closest to exhausting — the one you will actually hit.
A missing header does not mean "unlimited"
The headers are absent on endpoints with no budget, on unauthenticated requests, and in the rare case where the limiter itself is unavailable and the request is allowed through. Treat their absence as no information, not as unlimited budget.
When you hit it
{
"id": "request_rate_limited",
"message": "Too many requests. Retry in 27 seconds.",
"version": "v2026-08-17"
}429, with Retry-After in seconds. There is no errors array — nothing about your request was wrong.
A refused request still counts
Retrying into a 429 spends your hourly budget. A tight retry loop walks itself into the sustained limit instead of waiting out the per-minute one. Honour Retry-After.
Three different 429s
The id tells them apart, and they need different responses from you.
id | Meaning | What fixes it |
|---|---|---|
request_rate_limited | Too fast for this endpoint's tier | Wait Retry-After seconds |
rate_limit_exceeded | The global 10 req/s ceiling, or out of credits | Slow down, or top up / upgrade |
feature_not_in_plan | Your plan does not include this design type — animated needs Pro, print needs Suite | Upgrade — retrying never helps |
Branch on id, never on the message text.
Notes
The per-workspace budget belongs to the workspace, not the API key — minting more keys does not multiply it.
Limits are counted in fixed windows aligned to the clock, so X-RateLimit-Reset is the moment the current window rolls over, not a sliding countdown.
These numbers are a starting point and may be adjusted. X-RateLimit-Limit is authoritative for your workspace at the moment you read it; prefer it over anything hard-coded from this page.
Related
- Errors — the full error envelope and every
id - Create Import — the plan requirements behind
feature_not_in_plan
