Skip to content

Errors, Quotas & Rate Limits

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.

CodeWhen it happensWhat to do
400Missing required variable, or a value failed the template’s validation rulesFix the request; the detail names the variable
401Missing or invalid X-Api-KeyCheck the header and that the key isn’t revoked
403Share links aren’t on this plan, or the active-link cap is reachedUpgrade, or revoke a link you no longer need
404Template/project/share link not found, or not accessible to this keyVerify the ID and that the template is shared/owned
429Monthly quota or short-term rate limit exceededBack off and retry — see below
502The rendering service returned an error, or a share link couldn’t be publishedRetry with exponential backoff
503The rendering service or share storage is temporarily unavailableRetry with exponential backoff

These appear on POST /generate with delivery: "link", and on the share endpoints:

codeStatusCause
SHARING_NOT_AVAILABLE_ON_PLAN403The organization’s plan doesn’t include share links
SHARE_LINK_LIMIT_REACHED403The organization is at its cap of active links
SHARE_NOT_FOUND404No such link, or it belongs to another account
SHARE_UPLOAD_FAILED502The render succeeded but couldn’t be published
SHARE_REVOKE_FAILED502The published image couldn’t be deleted
SHARING_UNAVAILABLE503Share storage is temporarily unavailable

The two 502s fail in opposite directions, so handle them differently:

  • SHARE_UPLOAD_FAILED leaves 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 with delivery: "binary" instead.
  • SHARE_REVOKE_FAILED means the link may still resolve. Retry until it succeeds; don’t record the link as revoked.

These appear on the batch-jobs endpoints:

codeStatusCause
VARIABLE_VALIDATION_FAILED400A row failed the template’s validation — no job created, nothing charged
BATCH_ROW_LIMIT_EXCEEDED400More rows than your plan allows per job
BATCH_SIZE_EXCEEDED400The job exceeds the structural size ceiling
TEMPLATE_NOT_FOUND400Template doesn’t exist or isn’t accessible to this key
BATCH_INVALID_FILTER400Unknown status/source value, or invalid paging on the list or items endpoints
BATCH_INVALID_DELIVERY400delivery was something other than url or zip
BATCH_COMPLETION_CALLBACK_URL_INVALID400callbackUrl isn’t a publicly reachable https address — no job created, nothing charged
BATCH_NOT_FOUND404No such job, or it belongs to another account
BATCH_RESULT_NOT_FOUND404The completed job’s archive is no longer in storage
BATCH_RESULT_EXPIRED410The archive passed its 30-day retention window; the job stays readable
BATCH_IDEMPOTENCY_KEY_REUSED409The Idempotency-Key was already used for a different submission
BATCH_RESULT_NOT_READY409The job isn’t COMPLETED yet — keep polling
BATCH_JOB_ACTIVE409Delete refused while the job runs — cancel first
BATCH_RATE_LIMIT_EXCEEDED429Quota can’t cover the job, or the concurrent-job cap is reached
BATCH_DISPATCH_FAILED500Dispatch 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.

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: 10000
X-Quota-Remaining: 9831
X-Quota-Reset: 2026-07-01T00:00:00Z

When 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.

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: 1
X-RateLimit-Remaining: 0

Distinguish 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 429 (RATE_LIMIT_EXCEEDED), 502, and 503 with exponential backoff (e.g. 1s, 2s, 4s, 8s), honoring Retry-After when present.
  • Do not blindly retry 400, 401, 404, or 429 (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 / 503 is 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.