Errors
The problem details format, which failures are worth retrying, and every error code grouped by HTTP status with a suggested fix.
Errors are returned as problem details (RFC 9457) with the content type application/problem+json. Every error carries a stable machine-readable code; branch on that, never on the human-readable title or detail.
{
"type": "https://docs.dynamicdocumentapi.com/errors/template_runtime_error",
"title": "Template runtime error",
"status": 422,
"code": "template_runtime_error",
"detail": "'dict object' has no attribute 'total' (line 12, column 7)",
"errors": [
{ "path": "/data/items/0/qty", "code": "type_error", "message": "expected number" }
],
"render_id": "rnd_01J9ZM1X3F7R8K2C4V6B8N0P2Q",
"request_id": "req_01J9ZM4T6W8Y0A2C4E6G8J0K2M",
"doc_url": "https://docs.dynamicdocumentapi.com/errors/template_runtime_error"
}| Field | Description |
|---|---|
type | URI identifying the error type, such as https://docs.dynamicdocumentapi.com/errors/template_runtime_error |
title | Short human-readable summary |
status | The HTTP status code, repeated in the body |
code | Stable identifier to branch on. Every code is listed below. |
detail | What went wrong in this particular case, including template line and column where applicable |
errors | For validation failures: one entry per problem, with a JSON Pointer path, a code and a message |
render_id | Present when a render was created before the failure |
request_id | The request's ID, also in the X-Request-Id header. Include it in support requests. |
doc_url | Link to the documentation for this code |
Failed renders carry the same information. A sync render that fails returns 422 with the render error, and an async render ends with status: "failed" and an error object holding code, message and, for template problems, line, column and excerpt.
Handling errors
Retry these:
429after the number of seconds inRetry-After500and503, with exponential backoff;503also sendsRetry-After409 idempotency_in_progress, after a short pause, with the same idempotency key
Don't retry the others without changing the request: 400, 401, 402, 403, 404, 410, 413, 415, 422 and 423 describe something that will fail the same way again.
Always send an Idempotency-Key header so that a retry can never produce a second document, and log request_id next to your own identifiers.
const MAX_ATTEMPTS = 5;
async function createRender(body: unknown, idempotencyKey: string) {
for (let attempt = 1; ; attempt++) {
const response = await fetch("https://api-eu.dynamicdocumentapi.com/v1/renders", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DYNAMIC_DOCUMENT_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(body),
});
if (response.ok) return response.json();
const problem = await response.json().catch(() => ({}));
const retryable =
response.status === 429 || response.status >= 500 || problem.code === "idempotency_in_progress";
if (!retryable || attempt === MAX_ATTEMPTS) {
throw new Error(`${problem.code ?? response.status}: ${problem.detail ?? ""} (request ${problem.request_id ?? "?"})`);
}
const retryAfter = Number(response.headers.get("Retry-After"));
const delaySeconds = Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delaySeconds * 1000));
}
}400 Bad Request
| Code | Meaning | Fix |
|---|---|---|
invalid_json | The request body isn't valid JSON | Check quoting and encoding. In shells, watch for unescaped quotes inside -d '…'. |
validation_error | The JSON is valid but a field is missing, has the wrong type or is out of range | Read errors[]: each entry points at the offending field with a JSON Pointer |
401 Unauthorized
| Code | Meaning | Fix |
|---|---|---|
authentication_required | No API key was sent | Send Authorization: Bearer <key> or the X-API-Key header |
invalid_api_key | The key is malformed or unknown | Check for truncation, stray whitespace or a key from another workspace |
api_key_expired | The key passed its expiry date | Create a new key or extend the expiry |
api_key_revoked | The key was revoked, or a rolled key's grace period ended | Deploy the current key |
402 Payment Required
| Code | Meaning | Fix |
|---|---|---|
render_limit_reached | The Free plan's included renders are used up, or a paid workspace ran out of renders and no auto top-up can be added (monthly top-up limit reached, auto top-ups off, or a top-up payment outstanding) | Free: upgrade, buy a render pack, or wait for the next cycle. Paid: raise the monthly top-up limit or settle the open payment. Test keys keep working. See Plans and limits. |
plan_feature_unavailable | The request uses a feature that isn't part of your plan, such as PDF/UA, PDF/A or e-invoicing below Growth | Remove the option or upgrade |
free_workspace_limit | You already own two workspaces on the Free plan, the most one person can have. Free workspaces you deleted in the last 30 days still count, and so does taking over a free workspace from someone else | Upgrade one of your workspaces to a paid plan, then create the new one |
403 Forbidden
| Code | Meaning | Fix |
|---|---|---|
insufficient_scope | The key lacks a scope for this operation | Use a key with the right scope, for example render:write |
ip_not_allowed | The request came from an address outside the key's IP allowlist | Add the address range, or call from an allowed network |
test_key_required | The request needs a test key, for example rendering a template draft or sending test: true with a live key | Use a test key, or render a published version |
workspace_suspended | The workspace is suspended | Check billing and email, or contact support@dynamicdocumentapi.com |
workspace_deletion_scheduled | The workspace is scheduled for deletion, so its API keys are refused | The owner can cancel the deletion in the dashboard until it completes |
template_not_allowed_for_key | The key's template allowlist doesn't include this template | Add the template to the allowlist or use another key |
email_not_verified | Live rendering requires a verified email address | Verify the address; test keys work meanwhile |
region_not_enabled | Your workspace isn't enabled for the region of this API host, so renders, previews, PDF tools and e-invoices are refused here | Call the API host of your workspace's region, see Data residency, or contact support@dynamicdocumentapi.com |
feature_not_enabled | Email delivery isn't enabled for your workspace yet, so its connections and rules can't be created, changed or tested | Write to support@dynamicdocumentapi.com to have it enabled; reading, deleting and switching rules off work meanwhile |
invalid_signature | A signed link URL has no sig, or it doesn't match the URL | Sign the exact path and query with the link secret; any change to a parameter, the name or exp needs a new signature |
referrer_not_allowed | The page embedding a signed link isn't in the link's allowed_referrers | Add the site to allowed_referrers, or remove the list |
404 Not Found
| Code | Meaning | Fix |
|---|---|---|
not_found | The resource doesn't exist, or belongs to another workspace | Check the ID and the key's workspace |
template_not_found | No template with this ID | Check the template_id; GET /v1/templates lists them |
render_not_found | No render with this ID | Check the ID and the key's workspace; test keys only see test renders |
version_not_found | The template has no such version | List versions with GET /v1/templates/{id}/versions |
upload_not_found | The upload doesn't exist or has expired | Uploads expire 24 hours after creation; upload the file again |
408 Request Timeout
| Code | Meaning | Fix |
|---|---|---|
render_timeout | A sync render reached the plan's sync timeout and was canceled because timeout_behavior is cancel | Use mode: "async", simplify the document, or upgrade for a longer timeout |
409 Conflict
| Code | Meaning | Fix |
|---|---|---|
idempotency_in_progress | An earlier request with the same idempotency key is still running | Wait briefly and retry with the same key |
draft_revision_conflict | The template draft changed since you read it | Fetch the current draft and apply your change again |
conflict | The request doesn't fit the resource's current state, for example canceling a render that already finished, retrying a batch that is still running, or replaying a delivery to a deleted endpoint | Read the detail, which says what conflicts |
zero_retention | retry-failed on a zero-retention batch, which keeps no item data | Submit the failed items in a new batch |
item_files_expired | retry-failed on a batch with combine whose succeeded items' files are gone, so new combined files couldn't hold them. items lists those items' indexes. | Submit the failed items in a new batch. See combined files. |
email_connection_in_use | Email rules still use the connection you're deleting | Change or delete those rules first |
email_file_expired | A file of the email send is no longer hosted, so the send can't be repeated | Render the document again |
email_not_resendable | The send can't be repeated: its content wasn't kept, or it failed before sending | Fix the rule or the data and render again |
410 Gone
| Code | Meaning | Fix |
|---|---|---|
template_deleted | The template was deleted | Restore it from the trash in the dashboard, or use another template |
file_expired | The file was deleted at the end of its retention period or purged | Render again, or raise the retention period |
link_expired | A signed link or the URL's exp has expired | Sign a new URL, or extend the link's expires_at |
413 Payload Too Large
| Code | Meaning | Fix |
|---|---|---|
payload_too_large | The request body is larger than your plan allows | Trim the payload, use data_url for large data sets, or upgrade |
output_too_large | The generated file exceeds the limit for the delivery type (20 MB for binary, 10 MB for base64) or the maximum output size | Use url delivery, compress images, or split the document |
415 Unsupported Media Type
| Code | Meaning | Fix |
|---|---|---|
unsupported_media_type | The Content-Type isn't supported by this endpoint | Send application/json, or multipart/form-data when uploading files |
422 Unprocessable Entity
Most render failures land here. When a render was created before it failed, the body includes render_id.
| Code | Meaning | Fix |
|---|---|---|
template_syntax_error | The template can't be parsed | Fix the syntax at the reported line and column; the editor flags these live |
template_runtime_error | The template failed while rendering, for example an unknown filter, an unsupported operation, or an undefined value in strict mode | Check the line, column and excerpt against your data |
template_fuel_exhausted | The template exceeded its execution budget | Simplify nested loops, precompute values in your application, or upgrade |
data_schema_mismatch | The data doesn't match the template's JSON Schema | Read errors[] for the failing paths, or relax the schema |
idempotency_key_reused | The same idempotency key was used with a different body | Use a new key for a different request |
url_not_allowed | The target URL is blocked: a private or internal address, an unsupported scheme or a non-standard port | Use a publicly reachable HTTP or HTTPS URL on a standard port |
navigation_failed | The page couldn't be loaded | Check that the URL is reachable from the public internet and that any credentials in input.http are correct |
wait_timeout | The page never reached the wait condition within wait.timeout_ms | Check that the selector appears or the ready flag is set, raise the timeout, or use another wait strategy |
asset_fetch_failed | A required remote file couldn't be fetched, such as the source URL of a PDF tool | Check that the host is reachable and fast enough, or upload the file instead |
data_url_fetch_failed | The data_url couldn't be fetched | Check that the URL is public, returns JSON and responds quickly |
page_limit_exceeded | The PDF has more pages than your plan allows | Check for unexpectedly long data, split the document, or upgrade. Nothing is billed. |
attachment_failed | A file in pdf.attachments couldn't be loaded, for example because its URL fails or it is too large; the message names it | Check the attachment's source and size. See Attachments. |
invalid_pdf_source | A PDF passed to a PDF tool isn't a valid PDF | Check the source file |
pdf_password_required | The source PDF is encrypted | Supply the password, or unlock the file first |
scene_invalid | A canvas template's design can't be rendered, for example because the output would be larger than 16,384 pixels on a side | Read the message; for size problems, lower the artboard size or scale |
data_invalid | A canvas template's data isn't an object, or a required dynamic property is missing | Send an object with every required property |
email_template_error | A field of an email rule, such as its subject or recipients, can't be parsed | Fix the field at the reported line and column. See Email delivery. |
email_needs_hosted_file | Email rules were requested for a zero-retention render, which keeps no file to send | Leave out delivery.email, or render without zero retention |
render_failed | The render failed without a more specific code | Send us the render_id and request_id |
pdfua_validation_failed | The PDF doesn't pass veraPDF's PDF/UA-1 checks, often because of a missing title, language, alt text or table headers | Read the failed rules in result.conformance.pdfua. See PDF/UA. |
pdfa_conversion_failed | The PDF couldn't be made PDF/A conformant, for example because a font isn't embedded or the source is encrypted, or veraPDF found it non-compliant | Read the message, which names missing fonts, and the failed rules in result.conformance.pdfa. See PDF/A. |
einvoice_invalid | The invoice model breaks a rule of the e-invoice profile before any XML is written, such as a missing field the profile requires, a VAT category without a rate or a net_amount that doesn't add up | result.conformance.einvoice.errors lists every problem with a JSON path into the invoice. See E-invoicing. |
einvoice_validation_failed | The e-invoice validator rejected the generated XML | Read the rules in result.conformance.einvoice.errors; they usually concern content such as a missing Leitweg-ID or an invalid code |
invalid_pdf_source and pdf_password_required come from the PDF tools, and scene_invalid and data_invalid from canvas templates. The last four codes come from PDF/UA, PDF/A and e-invoice output: such renders fail without being billed and carry the validation report in result.
423 Locked
| Code | Meaning | Fix |
|---|---|---|
template_locked | The template is locked, so through the API its draft can't be replaced (PUT /v1/templates/{id}/draft) and it can't be deleted (DELETE /v1/templates/{id}) | Ask an Owner or Admin to unlock it in the dashboard |
429 Too Many Requests
| Code | Meaning | Fix |
|---|---|---|
rate_limited | Too many requests per second for your plan | Wait for Retry-After, smooth out bursts, or upgrade. See rate limits. |
concurrency_limited | Too many synchronous renders are running at once | Wait for Retry-After, use mode: "async", or lower your parallelism |
quota_exceeded | A signed link has used its quota_total renders | Raise the link's quota; cached URLs keep being served |
500 and 503
| Status | Code | Meaning | Fix |
|---|---|---|---|
| 500 | internal_error | Something failed on our side | Retry with the same idempotency key. If it persists, send us the request_id. |
| 503 | region_degraded | The region is running with reduced capacity | Retry after Retry-After; see https://status.dynamicdocumentapi.com |
| 503 | maintenance | Planned maintenance is in progress | Retry after Retry-After; see https://status.dynamicdocumentapi.com |
A render that the platform loses fails with the code render_lost: its job expired in the queue, or it stopped without a result well past its time limit. Nothing is billed; render it again.
Render warnings
Warnings don't fail a render. They appear in the render object's warnings array and in webhook payloads, and they are worth logging: they usually explain why output doesn't look the way you expect.
| Code | Meaning | What to check |
|---|---|---|
slow_asset | A remote asset took a long time to load | Host large images closer to the renderer, or embed them; slow assets also slow down renders |
asset_failed | An asset couldn't be loaded and is missing from the output | Check the URL and that the host is publicly reachable |
font_fallback_used | A canvas template uses a font that isn't available, so Noto Sans was used instead | Pick one of the bundled fonts, or add the font file to the template. HTML renders don't report fallback fonts; see common problems. |
header_footer_overlap | A header or footer is taller than its page margin, so it may overlap the body | Raise margin.top or margin.bottom, or lower the height. See height and margins. |
locale_unsupported | The engine doesn't support the locale you set | Use another BCP 47 tag, such as de-DE |
feature_unavailable | Something in the request or the template can't take effect and was ignored, such as a @page :first rule meant to hide the header on the first page | Read the message, which names what was ignored |
data_coerced | A canvas template couldn't use a data value as given, such as an unknown key or a value that had to be shortened; an invalid value keeps the designed one | Check the keys and values against the template's dynamic properties |
text_overflow | Text in a canvas template was cut off or overflows its box | Shorten the text or make the box larger |
storage_upload_failed | An upload to one of your storage destinations failed for good. The message says whether a copy remains: the hosted file, a copy kept for a day with hosted: false, or none under zero retention. | Check the destination's credentials and permissions |
einvoice_validation_warning | The e-invoice validator accepted the XML but reported warnings; the message names the rules | Read result.conformance.einvoice.warnings and fix the content if your recipient requires it |
einvoice_validation_unavailable | The e-invoice validator didn't answer in time, so the XML was delivered unvalidated | Validate the file yourself or render it again later |
pdfa_validation_unavailable | veraPDF didn't answer in time, so the PDF/A file was delivered unvalidated | Render it again later if you need the report |
pdfua_validation_unavailable | veraPDF didn't answer in time, so the PDF/UA file was delivered unvalidated | Render it again later if you need the report |
accessibility_validation_unavailable | A tagged PDF without pdfua comes without its PDF/UA report, because veraPDF didn't answer in time | Render it again later if you need the report |
einvoice_skipped_plan | The template embeds a ZUGFeRD e-invoice by default, but the plan doesn't include e-invoicing, so the PDF has none | Upgrade to Growth or higher, or send "output": {"einvoice": null} to render plain PDFs without the warning |
storage_skipped_plan | The workspace has a default storage destination, but the plan doesn't include storage destinations, so the files stayed hosted; under zero retention nothing was uploaded | Upgrade to Starter or higher, or set is_default to false on the destination |
email_skipped_plan | The template has email rules, but they need the Growth plan or higher, so no email was sent | Upgrade, or switch the rules off |
email_skipped_zero_retention | Zero-retention renders keep no file to email, so the template's email rules were skipped | Render without zero retention when the document should be emailed |
invoice_totals_unavailable | The template has a default e-invoice that this render doesn't embed, and the invoice at its data path couldn't be computed, so render.invoice is missing | Send the invoice at the template's data path (invoice by default); the message names the first problem |
Email sends report their own error codes in the email send object; see Email delivery.