Rate limits and idempotency
Understand per-key request limits and safely retry job creation.
Rate limits
Each API key has its own budget per minute, shared by reads and writes: 30 requests per minute for organizations on the free tier and 120 per minute for organizations with an active plan. Every authenticated response tells you where you stand:
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 12
X-RateLimit-Reset: 1790000060X-RateLimit-Reset is the Unix time (seconds) when the minute ends. When you run out, the API answers 429 with the code rate_limited, X-RateLimit-Remaining: 0 and a Retry-After header with the seconds to wait. Several keys do not share a budget: one busy key never slows another.
GET /jobs/{id}?wait= counts as one request however long it waits, so long-polling is much cheaper than polling every second. Better still, use webhooks.
Requests with a bad or missing key are throttled separately, per IP address: after 30 failed attempts in a minute, further requests from that address, even with a valid key, get 429 with details.reason set to ip_throttled until the minute ends. Good requests never count toward this. A per-key limit has details.reason set to key_rate_limited.
There is no limit on how many jobs a key may have running; jobs beyond the platform's current capacity simply wait as queued.
Idempotency
Send an Idempotency-Key header on POST /jobs so that retrying after a network failure cannot create a second job. The key is any 1–255 printable ASCII characters; use a unique value per intended job (an order id plus a counter works well). Keys are remembered for 24 hours and belong to the API key that used them.
- Same key, same body: you get the original response with its original status (
201) and the headerIdempotent-Replayed: true. No new job is created. - Same key, different body:
422 idempotency_conflict. - Same key while the first request is still running:
409 idempotency_conflictwithRetry-After: 1; try again in a moment. - A request that fails (for example
402 insufficient_creditsor a validation error) does not save the key, so you can fix the problem and retry with the same key.
curl -X POST "$BASE/jobs" \
-H "Authorization: Bearer $KEY" \
-H 'Idempotency-Key: order-842-job-1' \
-H 'Content-Type: application/json' \
-d '{"tasks":{"import-1":{"operation":"import/upload"},"convert-1":{"operation":"convert","input":"import-1","input_format":"png","output_format":"webp"},"export-1":{"operation":"export/url","input":"convert-1"}}}'Send the identical body when you retry. Other endpoints do not need a key: GET and DELETE are safe to repeat, and cancelling twice is answered with a clear 409.