DynamicDocumentAPI

Quickstart

Create a test API key, render your first PDF from HTML, get a signed download URL and render a stored template by ID.

View as Markdown

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

  1. Sign up at https://app.dynamicdocumentapi.com.
  2. Open API keys and create a key in test mode.
  3. 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.pdf

Save 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:

  • input describes what to render. The template engine replaced {{ name }} with the value from data.
  • output selects the format and PDF options such as the paper size.
  • delivery.type: "binary" returns the file as the response body. The X-Render-Id, X-Pages and X-Billed-Renders response headers describe the render.
  • The Idempotency-Key header 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.

  1. In the dashboard, create a template or start from one in the gallery.
  2. Publish it. Publishing creates version 1 and makes it the live version.
  3. 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": 3 in input. Without it, the live version 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 takes template_id, data and 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:

  1. Verify your email address. Live keys can't render until it is verified.
  2. Create a live key with only the scopes you need. A backend that renders and downloads documents needs render:write and renders:read. Restrict the key to your servers' IP ranges where possible.
  3. 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.
  4. Choose a plan and review your monthly top-up limit. See Plans and limits.
  5. Send an Idempotency-Key with every POST request, and reuse the same key when you retry.
  6. Handle errors deliberately. Retry 429 and 5xx responses after Retry-After, fix 4xx requests instead of retrying them, and log the request_id. See Errors.
  7. Use async mode and webhooks for long renders. Anything that may exceed your plan's sync timeout should use mode: "async". Verify webhook signatures.
  8. Decide how long files live. Set a workspace retention period, use delivery.expires_in for link lifetimes, or use zero-retention delivery for sensitive data.
  9. Subscribe to status updates at https://status.dynamicdocumentapi.com.

On this page