Quickstart
Create a test API key, render your first PDF from HTML, get a signed download URL and render a stored template by ID.
This guide takes about five minutes. You will create a test key, render a PDF from HTML, get a signed download link, and then render a template stored in your workspace.
The examples use cURL, TypeScript with the built-in fetch of Node.js 18 or later, and Python with the requests package.
1. Create an account and a test key
- Sign up at https://app.dynamicdocumentapi.com.
- Open API keys and create a key in test mode.
- Copy the key. It is shown only once.
Test keys work before you verify your email address. Test renders are free, carry a "TEST" watermark and are deleted after 24 hours, so you can experiment without using any of your renders.
2. Export the key
Keep the key out of your source code. The examples read it from an environment variable:
export DYNAMIC_DOCUMENT_API_KEY="dda_test_…"3. Render your first PDF
This request renders an HTML snippet with one variable and returns the PDF itself as the response body (delivery.type: "binary"):
curl https://api-eu.dynamicdocumentapi.com/v1/renders \
-H "Authorization: Bearer $DYNAMIC_DOCUMENT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"input": { "type": "html", "html": "<h1>Hello {{ name }}</h1>" },
"data": { "name": "World" },
"output": { "format": "pdf", "pdf": { "paper": { "size": "A4" } } },
"delivery": { "type": "binary" }
}' \
-o hello.pdfSave the TypeScript example as hello.mjs and run it with node hello.mjs. Open hello.pdf: it says "Hello World" and carries the test watermark.
What happened:
inputdescribes what to render. The template engine replaced{{ name }}with the value fromdata.outputselects the format and PDF options such as the paper size.delivery.type: "binary"returns the file as the response body. TheX-Render-Id,X-PagesandX-Billed-Rendersresponse headers describe the render.- The
Idempotency-Keyheader makes retries safe: repeating the request with the same key returns the original result instead of rendering again.
If the request fails
Errors are returned as JSON problem details with a code such as invalid_api_key or validation_error. With cURL, the error body ends up in hello.pdf, so open it in a text editor. See Errors for every code.
4. Get a signed download URL
For most applications it's more convenient to store the file and hand out a link. With delivery.type: "url" (the default whenever we keep a hosted copy), the response is a render object whose files have a signed, expiring URL:
curl https://api-eu.dynamicdocumentapi.com/v1/renders \
-H "Authorization: Bearer $DYNAMIC_DOCUMENT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"input": { "type": "html", "html": "<h1>Hello {{ name }}</h1>" },
"data": { "name": "World" },
"output": { "format": "pdf", "filename": "hello.pdf" },
"delivery": { "type": "url", "expires_in": 3600 }
}'The response looks like this (shortened):
{
"id": "rnd_01J9ZM1X3F7R8K2C4V6B8N0P2Q",
"object": "render",
"status": "succeeded",
"mode": "sync",
"test": true,
"region": "eu",
"input_type": "html",
"output_format": "pdf",
"files": [
{
"id": "file_01J9ZM4T6W8Y0A2C4E6G8J0K2M",
"format": "pdf",
"filename": "hello.pdf",
"bytes": 18342,
"pages": 1,
"url": "https://files-eu.dynamicdocumentapi.com/f/…",
"url_expires_at": "2026-09-17T16:06:34Z"
}
],
"billed_renders": 0,
"created_at": "2026-09-17T15:06:33Z",
"completed_at": "2026-09-17T15:06:34Z"
}Treat the URL as opaque and don't store it: it stops working at url_expires_at. Call GET /v1/renders/{id} to get a fresh URL for as long as the file is retained. The render object reference describes every field.
5. Render a stored template
Inline HTML is useful for experiments. In production you usually keep the design in a template, so that designers can change it without a deployment and every change is versioned.
- In the dashboard, create a template or start from one in the gallery.
- Publish it. Publishing creates version
1and makes it theliveversion. - Copy the template ID (
tpl_…) from the editor.
Then render it with your data:
curl https://api-eu.dynamicdocumentapi.com/v1/renders \
-H "Authorization: Bearer $DYNAMIC_DOCUMENT_API_KEY" \
-H "Idempotency-Key: invoice-2026-0042" \
-H "Content-Type: application/json" \
-d '{
"input": { "type": "template", "template_id": "tpl_01J9ZK3M7Q8V5W2X4Y6Z8A0B1C" },
"data": {
"number": "2026-0042",
"customer": { "name": "Example GmbH" },
"items": [ { "description": "Consulting", "quantity": 2, "unit_price": 450 } ]
},
"output": { "format": "pdf", "filename": "invoice-{{ data.number }}.pdf" }
}'The keys in data must match the variables your template uses. GET /v1/templates/{id}/schema returns the JSON Schema of the data a template expects.
A few variations:
- Pin a version with
"version": 3ininput. Without it, theliveversion is used, so publishing a new version changes future renders. - Render the unpublished draft with
"version": "draft"and a test key. - Use the shorter convenience endpoint
POST /v1/pdf/from-template, which takestemplate_id,dataand PDF options at the top level. See convenience endpoints. - Here the idempotency key is derived from the invoice number, so a retried request can never produce a second invoice PDF.
6. Go live
Before you switch to a live key, work through this list:
- Verify your email address. Live keys can't render until it is verified.
- Create a live key with only the scopes you need. A backend that renders and downloads documents needs
render:writeandrenders:read. Restrict the key to your servers' IP ranges where possible. - Store the key as a secret. Use your platform's secret manager or environment variables, and never send the key to browsers or mobile apps.
- Choose a plan and review your monthly top-up limit. See Plans and limits.
- Send an
Idempotency-Keywith every POST request, and reuse the same key when you retry. - Handle errors deliberately. Retry
429and5xxresponses afterRetry-After, fix4xxrequests instead of retrying them, and log therequest_id. See Errors. - Use async mode and webhooks for long renders. Anything that may exceed your plan's sync timeout should use
mode: "async". Verify webhook signatures. - Decide how long files live. Set a workspace retention period, use
delivery.expires_infor link lifetimes, or use zero-retention delivery for sensitive data. - Subscribe to status updates at https://status.dynamicdocumentapi.com.