Public API

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: 1790000060

X-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 header Idempotent-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_conflict with Retry-After: 1; try again in a moment.
  • A request that fails (for example 402 insufficient_credits or 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.

On this page