Errors, Quotas & Rate Limits
Error format
Section titled “Error format”Errors are returned as RFC 9457 application/problem+json:
{ "type": "about:blank", "title": "Variable validation failed", "status": 400, "code": "VALIDATION_ERROR", "detail": "Required variable 'discount_code' was not supplied.", "requestId": "req_01J9Z6Q8X..."}Always log requestId — it lets support trace a specific request. Branch your error handling on the numeric status and the machine-readable code.
Status codes
Section titled “Status codes”| Code | When it happens | What to do |
|---|---|---|
400 | Missing required variable, or a value failed the template’s validation rules | Fix the request; the detail names the variable |
401 | Missing or invalid X-Api-Key | Check the header and that the key isn’t revoked |
403 | Share links aren’t on this plan, or the active-link cap is reached | Upgrade, or revoke a link you no longer need |
404 | Template/project/share link not found, or not accessible to this key | Verify the ID and that the template is shared/owned |
429 | Monthly quota or short-term rate limit exceeded | Back off and retry — see below |
502 | The rendering service returned an error, or a share link couldn’t be published | Retry with exponential backoff |
503 | The rendering service or share storage is temporarily unavailable | Retry with exponential backoff |
Share link error codes
Section titled “Share link error codes”These appear on POST /generate with delivery: "link", and on the share endpoints:
code | Status | Cause |
|---|---|---|
SHARING_NOT_AVAILABLE_ON_PLAN | 403 | The organization’s plan doesn’t include share links |
SHARE_LINK_LIMIT_REACHED | 403 | The organization is at its cap of active links |
SHARE_NOT_FOUND | 404 | No such link, or it belongs to another account |
SHARE_UPLOAD_FAILED | 502 | The render succeeded but couldn’t be published |
SHARE_REVOKE_FAILED | 502 | The published image couldn’t be deleted |
SHARING_UNAVAILABLE | 503 | Share storage is temporarily unavailable |
The two 502s fail in opposite directions, so handle them differently:
SHARE_UPLOAD_FAILEDleaves nothing behind — no link was created, and nothing is publicly reachable. Retrying is safe, but it re-renders and consumes another render. If you only need the image, retry withdelivery: "binary"instead.SHARE_REVOKE_FAILEDmeans the link may still resolve. Retry until it succeeds; don’t record the link as revoked.
Batch job error codes
Section titled “Batch job error codes”These appear on the batch-jobs endpoints:
code | Status | Cause |
|---|---|---|
VARIABLE_VALIDATION_FAILED | 400 | A row failed the template’s validation — no job created, nothing charged |
BATCH_ROW_LIMIT_EXCEEDED | 400 | More rows than your plan allows per job |
BATCH_SIZE_EXCEEDED | 400 | The job exceeds the structural size ceiling |
TEMPLATE_NOT_FOUND | 400 | Template doesn’t exist or isn’t accessible to this key |
BATCH_INVALID_FILTER | 400 | Unknown status/source value, or invalid paging on the list or items endpoints |
BATCH_INVALID_DELIVERY | 400 | delivery was something other than url or zip |
BATCH_COMPLETION_CALLBACK_URL_INVALID | 400 | callbackUrl isn’t a publicly reachable https address — no job created, nothing charged |
BATCH_NOT_FOUND | 404 | No such job, or it belongs to another account |
BATCH_RESULT_NOT_FOUND | 404 | The completed job’s archive is no longer in storage |
BATCH_RESULT_EXPIRED | 410 | The archive passed its 30-day retention window; the job stays readable |
BATCH_IDEMPOTENCY_KEY_REUSED | 409 | The Idempotency-Key was already used for a different submission |
BATCH_RESULT_NOT_READY | 409 | The job isn’t COMPLETED yet — keep polling |
BATCH_JOB_ACTIVE | 409 | Delete refused while the job runs — cancel first |
BATCH_RATE_LIMIT_EXCEEDED | 429 | Quota can’t cover the job, or the concurrent-job cap is reached |
BATCH_DISPATCH_FAILED | 500 | Dispatch to the renderer failed — the charge was refunded; retry |
Note the deliberate split on results: 409 BATCH_RESULT_NOT_READY means wait longer, 410 BATCH_RESULT_EXPIRED means this job’s archive aged out after 30 days, and 404 BATCH_RESULT_NOT_FOUND means gone. Cancelling a job refunds every render that wasn’t completed — see Batch Rendering.
A completion callback that can’t be delivered doesn’t produce an error response — there’s no request of yours to fail. Its outcome shows up on GET /api/v1/batch-jobs/{jobId} under completionCallback, with the reason in lastError.
Monthly render quota
Section titled “Monthly render quota”Every workspace has a monthly render quota tied to its plan. Only image generation consumes quota — the read endpoints (list/get templates) are free.
Each generate response includes:
X-Quota-Limit: 10000X-Quota-Remaining: 9831X-Quota-Reset: 2026-07-01T00:00:00ZWhen the quota is exhausted, generation returns 429 with:
Retry-After: 1209600 # seconds until the quota resets (start of next month)X-RateLimit-Remaining: 0{ "title": "Monthly quota exceeded", "status": 429, "code": "QUOTA_EXCEEDED", "detail": "Monthly render quota exceeded. Quota resets at the start of next month."}A quota 429 will not succeed on retry until the reset time — upgrade your plan or wait. Track X-Quota-Remaining to avoid hitting the wall mid-campaign. See Usage & Quotas.
Short-term rate limit
Section titled “Short-term rate limit”Independent of the monthly quota, requests are throttled per second to protect the service. Exceeding the rate returns 429 with code: RATE_LIMIT_EXCEEDED and a short Retry-After (seconds):
Retry-After: 1X-RateLimit-Remaining: 0Distinguish the two 429 cases by the code field: RATE_LIMIT_EXCEEDED is transient (retry after a short pause), while QUOTA_EXCEEDED is not (wait for the monthly reset).
Retry guidance
Section titled “Retry guidance”- Retry
429(RATE_LIMIT_EXCEEDED),502, and503with exponential backoff (e.g. 1s, 2s, 4s, 8s), honoringRetry-Afterwhen present. - Do not blindly retry
400,401,404, or429(QUOTA_EXCEEDED) — fix the cause instead. - The single-render endpoint has no server-side request deduplication — every generate call re-renders and consumes quota. Quota charged for a render that fails with
502/503is refunded automatically, so retrying those is safe. But a retry after a client-side timeout on a call that actually succeeded will consume a second render, so track your own request IDs and deduplicate before retrying. See Generating images. - Batch submissions are deduplicated when you send an
Idempotency-Key— a retry with the same key returns the original job without billing or rendering twice. Always send one from automation. See Batch Rendering.