Errors

Full error code reference.

All errors are JSON with a stable machine-readable error code. Codes never change once shipped. Branch on error, never on the HTTP status alone.

Error shape

json
{
  "error": "template_forbidden",
  "message": "template_id does not belong to the workspace that owns this API key",
  "hint": "List the templates this key can see with GET /v1/templates, and copy the id from there.",
  "docs_url": "https://pixldocs.com/docs/api/templates",
  "request_id": "9426f621-3a96-4253-8f60-b2d8f4cd6a4a"
}
  • hint and docs_url are on every error: the hint is the next action in one sentence, the link is the page that explains it. They are for humans — never branch on them.
  • message and request_id are present on every error the API answers, except the three 401s, which carry error, message and docs_url but no request_id — a 401 means we do not know who called, so there is no account for support to look the id up against. A 503 service_unavailable is answered by the edge while the API restarts, so it carries a message but no request_id — there was no request to record.
  • Quote request_id in any support request — it is how a render is found in our logs.

Full reference

400invalid_template_idtemplate_id missing or not a UUID.
400invalid_idA path id (a render or job id) is not a UUID.
400invalid_jsonRequest body is not valid JSON. The message names the position of the syntax error.
400invalid_requestThe body, or one named field in it, has the wrong shape — the body carries field with the name of the offender.
400missing_fieldA required field is absent — html on /v1/render/html, url on /v1/render/url. The body names the field.
400unknown_fieldsdata carries keys the template does not bind. Nothing rendered, no credit used. The body lists unknown_fields and accepted_keys.
400invalid_query_paramA query parameter is present but not a value this endpoint accepts. Nothing is filtered and no list is returned — the message lists the allowed values.
400unknown_query_paramsA query parameter this endpoint does not accept was sent. Refused rather than ignored, so a mistyped filter can never look like a result.
400invalid_formatformat is not one of pdf, png or webp.
400invalid_scalescale is not a number inside the allowed range. Applies to image formats.
400invalid_versionversion is not published or draft.
400invalid_urlurl is not a valid http(s) URL. Applies to /v1/render/url and webhook endpoints.
400invalid_eventsA webhook endpoint was sent an events list that is not a non-empty subset of the supported event names.
400invalid_expect_selectorexpect_selector is not a CSS selector string of at most 200 characters.
400html_too_largehtml plus styles exceeds the inline limit. Nothing rendered, no credit used. Save the markup as a template and render by template_id.
400size_unsupported_for_htmlsize, width or height was sent for an HTML template. HTML templates size themselves through page_setup; those fields apply to canvas templates only.
400unsupported_kindThe template is a canvas template and the endpoint only edits HTML templates.
400invalid_itemsA batch was sent with an empty or non-array items list.
400too_many_itemsA batch carries more than 100 items.
400invalid_idempotency_keyIdempotency-Key longer than 255 characters.
400batch_rejectedAt least one item in a batch is unacceptable — unknown fields, or a template_id that is not a UUID. The whole batch is rejected and no job is queued; the body lists the failing items by index. Note the status is 404, not 400, when the failing item names a template that does not exist.
400liquid_syntax_errorThe template markup does not parse. The body carries line and col. Author error — the caller cannot fix it by retrying.
400liquid_render_errorThe markup parsed but failed while rendering with this data — usually a tag reading a field the data does not carry in the expected shape.
400liquid_filter_errorA filter refused its input — for example barcode or qr given a value it cannot encode. The message names the filter. Nothing rendered, no credit used.
400liquid_partials_disabledThe markup uses include or render. Partials are not available in HTML templates; inline the markup.
400undeclared_fieldsThe markup reads a name that no @field, @derived or @section declares, so no caller could ever send it and every render would answer unknown_fields. The message names each one and hands back a declaration block to paste. Refused on create and on update.
401missing_bearer_tokenNo Authorization header.
401invalid_api_keyBearer token not recognized.
401api_key_revokedKey was revoked from Account Settings.
401api_key_owner_missingThe key is real but the account that created it no longer exists, so nothing it owns can be read or written. Sign in and mint a new key.
401invalid_sessionA browser session token could not be verified. Sign in again and retry. API keys never see this code.
402insufficient_creditsWorkspace owner is out of credits.
402workspace_cap_exceededThe workspace has spent its monthly credit cap. The balance can still be positive — raise the cap in Workspace Settings. Nothing rendered, no credit used.
403template_forbiddentemplate_id belongs to a different workspace.
403feature_disabledThe account does not have the feature the endpoint needs enabled. The body names the feature.
400url_blockedA URL you supplied resolves to a loopback, private, link-local or otherwise non-public address, so this server will not call it. Webhook endpoints are checked when you register them and again on every delivery; a delivery refused this way is terminal and is not retried. Point the endpoint at a publicly reachable URL.
403demo_key_read_onlyThe public demo key cannot create or change templates.
403demo_key_published_onlyThe public demo key renders published gallery templates only — not your own HTML, URLs or drafts.
400demo_key_sync_onlyThe public demo key returns documents directly; async and save_to_renders need your own key.
404template_not_foundtemplate_id does not exist.
404template_config_not_foundThe template exists but has no stored configuration to render — it was never saved.
404render_not_foundAsync render job id does not exist, has expired, or belongs to another workspace.
404not_foundThe addressed row — a webhook endpoint or a delivery — does not exist, is disabled, or belongs to another workspace.
409idempotency_in_progressAn earlier request with the same Idempotency-Key is still running. Retry the same key after Retry-After.
413payload_too_largeThe request body is over the limit for the endpoint (4 MB on most routes, 12 MB on /v1/render-form, 50 MB at the edge). Nothing rendered, no credit used. Upload large images with upload_asset and send the URL instead of inlining the bytes. A body over the 50 MB edge limit is answered by the edge, so it carries no request_id.
422idempotency_key_reuseThe same Idempotency-Key was sent with a different body. Nothing rendered. Use a new key.
429rate_limitedToo many requests in the current one-minute window for this key. The allowance is per key and lower on a test key; the response carries Retry-After, limit_per_min and the RateLimit-* headers, which are the authority on your own limit.
500render_failedThe render itself failed. The credit is refunded. Retry with backoff.
500html_render_failedThe renderer refused inline HTML for a reason it did not name. The credit is refunded. Retry with backoff.
500url_render_failedThe renderer could not turn the given URL into a document. Check the URL is publicly reachable. The credit is refunded.
500render_returned_no_fileThe renderer answered but produced no bytes. Nothing is stored and the credit is refunded. Retry.
500enqueue_failedAn async render could not be put on the queue. No job exists and no credit was used. Retry.
500lookup_failedA render lookup could not read the archive. Nothing was charged. Retry.
500pdf_render_not_configuredThe render service is not configured on this deployment. Nothing rendered, no credit used. This is ours to fix — quote request_id.
500internal_errorThe request failed before it reached a handler. Quote request_id to support.
500usage_unavailableGET /v1/usage could not read the ledger. Nothing was charged. Retry.
503service_unavailableThe API itself is briefly unreachable, usually for under a minute during a deploy. Answered by the edge, so no request_id. Nothing rendered, no credit used. Retry after Retry-After.
503render_unavailableThe render service is briefly unreachable, usually during a deploy. Nothing rendered, no credit used. Retry the same request.
504render_timeoutThe page did not finish rendering inside the timeout. Nothing was returned and the credit was refunded. Simplify remote assets, then retry.

Retry policy

Retry on 429, 500, and 503 with exponential backoff (1s → 2s → 4s → 8s, max 5 attempts). Honour Retry-After when it is present. Do not retry 400/401/402/403/404/422 — they will fail identically. A 409 idempotency_in_progress is the one conflict worth retrying: send the same Idempotency-Key again after Retry-After and you will get the original response, not a second render.