To generate a PDF invoice from JSON with DocMake, POST the invoice data to https://app.docmake.io/api/v1/render with a bearer API key, a template_id, and "format": "pdf". The response body is the finished PDF file. There is no job to poll and no second call to fetch the result: a single render returns the binary synchronously.
You need three things before the first call: a DocMake account (the free plan includes full API access), an API key from Settings, API Keys, and a template ID. Everything else in this guide is a parameter on that one endpoint.
Step 1: get an API key
Keys are created in the app under Settings, API Keys. Every key is prefixed dm_ and is shown exactly once, because only a SHA-256 hash of it is stored. If you lose it, revoke it and create another.
A key belongs to a team, not to a person. Any request made with it acts as that team’s workspace, which is what you want for a server-side integration. The Free plan allows 2 keys; Pro is unlimited.
Step 2: get a template ID
A template is the document design: the layout, the fixed text, and the fill-in fields. You build it once in the visual editor (or import an existing Word file), then reuse it for every render. Template IDs look like tmpl_ne1EfQns2YBH, and you copy one from the editor.
If you want to see the fields a template expects before you write any mapping code, ask the API:
curl https://app.docmake.io/api/v1/templates/tmpl_ne1EfQns2YBH/form-schema \
-H "Authorization: Bearer dm_your_api_key"
That returns template_id, name, and a fields array. Each field carries a key (the dotted path where its value belongs in your data payload), a label, a type, whether it is required, and its default. Array fields nest their per-item fields under their own fields key, so the response is a map of exactly the JSON you need to send.
The invoice template in our gallery is a good reference shape: scalar fields for the header block and totals, plus an items array that expands into one table row per entry.
Step 3: render the PDF
curl -X POST https://app.docmake.io/api/v1/render \
-H "Authorization: Bearer dm_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"template_id": "tmpl_ne1EfQns2YBH",
"format": "pdf",
"strict": true,
"data": {
"invoice_number": "INV-2026-0142",
"issue_date": "August 8, 2026",
"due_date": "September 7, 2026",
"client_name": "Acme Corp",
"client_address": "540 Market Street, San Francisco, CA 94104",
"items": [
{ "description": "Design services", "qty": "12", "unit_price": "$150.00", "amount": "$1,800.00" },
{ "description": "Development sprint", "qty": "1", "unit_price": "$2,600.00", "amount": "$2,600.00" }
],
"total": "$4,400.00",
"payment_terms": "30 days"
}
}' \
--output invoice.pdf
That is the whole integration. invoice.pdf is on disk.
The request body, parameter by parameter
| Parameter | Required | Notes |
|---|---|---|
template_id | yes | The tmpl_ public ID of the template to render. |
data | yes | The key must be present. An empty object is legal; every variable then falls back to its default or fallback text. |
format | no | docx or pdf. Defaults to docx, so pass pdf explicitly. |
filename | no | Output name without extension, max 255 characters. Defaults to the template name. |
locale | no | BCP 47 tag such as en-US or de-DE, used for date and number formatting. Defaults to the workspace setting. |
page_size | no | a4, letter or legal. Defaults to the workspace setting. |
strict | no | Fail with a list of missing variables instead of rendering blanks. Defaults to false. |
The one that trips people up is format. Omit it and you get a DOCX, because the API was built DOCX-first. If you want a PDF, say so.
What comes back
On success you get a 200 and the raw file, plus a few headers worth reading:
Content-Type:application/pdf, or the Word MIME type for DOCX.Content-Disposition:attachment; filename="...", so you can reuse the server-side filename.X-DocMake-Render-Time-Ms: how long the engine took, in milliseconds.X-DocMake-Warnings: a JSON array of non-fatal engine warnings, for example a variable that resolved to nothing.X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset: your per-minute budget.
X-DocMake-Warnings is the header to log. A render can succeed and still be wrong, because a missing variable resolves to its fallback text or to an empty string. The warnings tell you which ones.
Use strict mode so blanks fail loudly
Passing "strict": true runs the payload against the template’s variable contract before rendering. If anything has no value and no default, you get a 422 instead of a document with holes in it:
{
"error": {
"type": "missing_data",
"message": "2 variable(s) have no data. Provide values or render without strict.",
"missing": [
{ "key": "client_name", "message": "Fill in 'client_name'." },
{ "key": "due_date", "message": "Fill in 'due_date'." }
]
}
}
A strict failure does not count against your monthly document quota, so there is no cost to being careful. Variables that declare a default in the editor are filled in before the check runs, so a defaulted variable never counts as missing.
The other error responses
401witherror.typeofauthentication_error: the key is missing, malformed, or revoked. Keys must start withdm_.404: no template with that ID in this team’s workspace.422: either the strict-mode shape above, or Laravel-style validation withmessageand anerrorsobject when a parameter is the wrong type.429: two different shapes. Per-minute throttling returnserror.typeofrate_limit_errorwith aRetry-Afterheader. Blowing your monthly document or daily API-call quota returns a flat body with"error": "quota_exceeded", plususedandlimit.503withrender_busyandRetry-After: 5: the render pool is saturated. Retry.
The same call in Node.js and Python
import { writeFile } from 'node:fs/promises';
const res = await fetch('https://app.docmake.io/api/v1/render', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.DOCMAKE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
template_id: 'tmpl_ne1EfQns2YBH',
format: 'pdf',
strict: true,
data: invoice,
}),
});
if (!res.ok) {
throw new Error(`Render failed: ${res.status} ${await res.text()}`);
}
console.log('rendered in', res.headers.get('X-DocMake-Render-Time-Ms'), 'ms');
await writeFile('invoice.pdf', Buffer.from(await res.arrayBuffer()));
import os
import requests
res = requests.post(
"https://app.docmake.io/api/v1/render",
headers={"Authorization": f"Bearer {os.environ['DOCMAKE_API_KEY']}"},
json={
"template_id": "tmpl_ne1EfQns2YBH",
"format": "pdf",
"strict": True,
"data": invoice,
},
)
if res.status_code == 422:
raise ValueError(res.json()["error"]["missing"])
res.raise_for_status()
with open("invoice.pdf", "wb") as f:
f.write(res.content)
Billing runs: use the batch endpoint
Invoicing is bursty. Instead of firing 400 single renders at the start of the month, POST once to /api/v1/render/batch with a template_id and an items array of up to 100 entries, each with its own data and optional filename. You get a 202 back with a job_id, then either poll GET /api/v1/render/jobs/{job_id} or supply a webhook_url and let the job call you.
When the job completes, GET /api/v1/render/jobs/{job_id}/download returns the single file, or a ZIP if the batch produced several. Download links stay valid for the 7-day document history window. Per-item status is reported individually, so one bad row does not sink the run.
Rate limits, in plain numbers
Free allows 30 requests per minute and 500 API calls per day, against a 1,000 document monthly quota. Pro allows 120 requests per minute, 50,000 API calls per day, and 10,000 documents a month. GET /api/v1/usage returns where you stand right now: plan, billing period, and used-versus-limit counts for renders, templates, credits and API calls, with null meaning unlimited.
Where to go next
- The full endpoint list and code samples in four languages live on the developers page.
- If your data has optional sections or repeating rows, read template variables, conditionals and loops explained.
- Not sure whether to ship PDF or Word to your customers? See DOCX vs PDF.
- Related gallery templates: quote, receipt, credit note, purchase order.