Templates
Design invoices, receipts, labels and certificates once in the dashboard, then create PDFs by sending JSON.
- Build or customise a template under Templates in the dashboard. The library has 32 starters: invoices, quotes, purchase orders, receipts, bills and statements, payslips and HR letters, shipping and address labels, QR codes, product and asset labels, badges and ID cards, event tickets and vouchers, menus, certificates, letters and envelopes.
- Each template has fields. A field's key is the JSON property you send, for example
invoice_numberorcustomer.name. - Render with
POST /templates/{template_id}/render. Each successful render is charged as one PDF, the same as/quickjob.
This endpoint renders a template that already exists in your account. It doesn't take a design in the request. Before you call it:
- In the dashboard, open Templates and pick a starter from the Template library, click New blank template, or click New HTML template (beta) to write the template in your own HTML.
- Adjust the design and fields, then Save.
- Copy the template's ID. It appears on the template's card in My templates (with a copy button), in Use via API, and in the editor's address bar (
/templates/<template_id>/edit).
Calling render with an ID that doesn't exist in your account returns 404 TEMPLATE_NOT_FOUND. Editing a saved template changes the output of later calls; the ID stays the same. Deleting it makes those calls fail.
Quick Example
Replace YOUR_TEMPLATE_ID with your template's ID and send values for its fields:
curl -X POST https://api.podpdf.com/templates/YOUR_TEMPLATE_ID/render \
-H "X-API-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"data": {
"invoice_number": "INV-1042",
"issue_date": "2026-09-13",
"customer": { "name": "Acme Ltd", "address": "1 Main St\nSpringfield" },
"items": [
{ "description": "Design work", "quantity": 10, "unit_price": 85 },
{ "description": "Hosting", "quantity": 1, "unit_price": 20 }
],
"tax_rate": 20
}
}' \
--output INV-1042.pdf
The totals, currency formatting, page numbers and table headers on every page all come from the template, so you only send the values.
Open a template and click Use via API. It shows the endpoint for that template and an example body with every field, in cURL, JavaScript and Python.
Authentication
Required. Send an API key or a dashboard ID token:
X-API-Key: your_api_key_here
Request
Endpoint
POST https://api.podpdf.com/templates/{template_id}/render
Body
| Field | Type | Required | Description |
|---|---|---|---|
data | object | Yes | Values for the template's fields. Nested keys such as customer.name are nested objects. List fields are arrays of objects. Extra keys are ignored. Up to 1 MB. |
store | boolean | No | true stores the PDF and returns a download link (valid for 1 hour) instead of the file |
filename | string | No | File name for the Content-Disposition header |
Value formats
| Field type | Send | Example |
|---|---|---|
| Text, Multi-line text | string (numbers are accepted) | "Acme Ltd", "Line 1\nLine 2" |
| Number | number or numeric string | 12.5 |
| Money amount | number, in the template currency | 1234.56 → $1,234.56 or 1.234,56 € |
| Percentage | number, where 20 means 20% | 20 |
| Date | ISO date | "2026-09-13" |
| Image URL | public https:// URL (PNG or JPEG, up to 2 MB) or data:image/…;base64,… | "https://cdn.example.com/logo.png" |
| List | array of objects | [{ "description": "Design", "quantity": 2 }] |
Response
Success (default)
200 OK with the PDF as the body.
| Header | Description |
|---|---|
Content-Type | application/pdf |
X-Job-Id | Job ID, visible in GET /jobs |
X-PDF-Pages | Number of pages |
Success with store: true
{
"job_id": "3f5c2a7e-8b1d-4c9e-a2f4-6d7e8f9a0b1c",
"template_id": "01J8ZK3QW5V7N2B4X6C8D0E2F4",
"pages": 2,
"truncated": false,
"download_url": "https://…",
"download_url_expires_at": "2026-09-13T13:00:00.000Z"
}
Invalid data
Every problem is listed so you can fix them all at once. Nothing is charged.
{
"error": {
"code": "TEMPLATE_DATA_INVALID",
"message": "The data does not match the template fields",
"details": {
"errors": [
{ "field": "invoice_number", "error": "is required" },
{ "field": "items[1].quantity", "error": "must be a number" },
{ "field": "customer.logo_url", "error": "must be an https URL or a base64 data:image URI" }
]
}
}
}
Other errors
| Status | Code | Meaning |
|---|---|---|
| 400 | TEMPLATE_DATA_TOO_LARGE | data is over 1 MB |
| 400 | PAGE_LIMIT_EXCEEDED | The document has more pages than your limit |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 403 | INSUFFICIENT_CREDITS, RATE_LIMIT_EXCEEDED | Same as /quickjob |
| 404 | TEMPLATE_NOT_FOUND | Wrong ID, or the template belongs to another account |
| 408 | QUICKJOB_TIMEOUT | Rendering took longer than 30 seconds |
See Error handling for retries.
HTML templates (beta)
A template written in your own HTML renders through this same endpoint, with the same request, response, errors and price. Two things differ:
400 TEMPLATE_DATA_INVALIDis also returned when the data makes the template repeat more than 50,000 rows in total, or produce a document larger than 12 MB.- Totals are not calculated: send every value the template prints.
GET /templates and GET /templates/{template_id} return kind: "html" for these templates and "blocks" for visual ones. See the guide for the tags.
Page sizes
Page size, orientation and margins are part of the template, so the same JSON always produces the same layout.
| Preset | Size |
|---|---|
| A3, A4, A5 | ISO sizes |
| US Letter, US Legal | 8.5 × 11 in, 8.5 × 14 in |
| Receipt 80 mm, Receipt 58 mm | Thermal roll widths; the length follows the content |
| Shipping label 4 × 6 in | 101.6 × 152.4 mm |
| Custom | Any width 25–1200 mm and height 25–2000 mm, or automatic length |
Managing templates from code
The dashboard is the usual way to create and edit templates. The management routes need a dashboard ID token rather than an API key:
| Method | Path | Purpose |
|---|---|---|
GET | /templates | List your templates |
GET | /templates/{template_id} | Get a template and its definition (including fields) |
POST | /templates | Create from a definition or from a starter ({ "from_starter": "invoice-a4" }) |
PUT | /templates/{template_id} | Replace the definition (expected_version prevents overwriting someone else's edit). A template keeps its kind: a visual template cannot be replaced by an HTML one, or the other way round. |
DELETE | /templates/{template_id} | Delete |
GET | /templates/library | List starters |