Use the dashboard to design templates and move them between environments, or integrate PDFEx programmatically with the REST API.
After you log in, the secure area at /secure is your home base. Most workflows start with a template, then optionally connect your app via API keys.
POST /api/pdf/token or POST /api/templates/:id/preview.| Page | What it does |
|---|---|
| /secure/templates | List templates, export/import bundles, delete, open designer |
| /secure/design | Visual editor for pdfme layouts or HTML + Handlebars |
| /secure/generate | Pick a template, fill variables, download a test PDF |
| /secure/keys | Create and revoke API keys for REST access |
| /secure/tokens | View recent PDF generation requests |
| /secure/upgrade | View or change subscription tier and limits |
| Tier | Monthly PDF requests | Max templates |
|---|---|---|
| Hobby | 100 | 2 |
| Starter | 2,500 | 5 |
| Pro | 5,000 | 15 |
| Business | 50,000 | 35 |
| Max | 1B | 5,000 |
Move templates from staging to production (or between any two PDFEx deployments) without copying the database. Exports are saved as .pdfex-templates.json files containing template content, variables, sample data, custom fonts, and engine settings — but not environment-specific IDs.
.pdfex-templates.json file.template_id values accordingly. API keys are not exported; create new keys on each environment.{
"pdfex_export_version": 1,
"exported_at": "2026-06-28T12:00:00.000Z",
"templates": [
{
"name": "Invoice",
"engine": "html",
"html_template": "<html>...</html>",
"variables": ["invoice_number", "customer_name"],
"sample_data": { "invoice_number": "INV-001" },
"fonts": [{ "name": "CustomFont", "data": "<base64>" }],
"page_format": "Letter",
"is_public": false
}
]
}/api/templates/:id/export/api/templates/import{
"bundle": { "pdfex_export_version": 1, "templates": [...] },
"mode": "upsert"
}// mode: "create" (default) or "upsert" (match by template name)
// Response: { "result": { "created": [], "updated": [], "skipped": [] } }All API requests require an API key. Create and manage keys in the dashboard at API Keys. Include the key in the Authorization header. All responses use the envelope { "success": boolean, ... }. Errors include error and often details — field-level messages agents can act on.
Authorization: Bearer pdfex_<YOUR_API_KEY>Send runtime data to a saved template and get a watermarked PDF back immediately — no token, no credit charge. Use this when your app needs to show end users a preview before they commit to a production PDF.
curl -X POST https://pdfex.io/api/templates/TEMPLATE_ID/preview \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{
"data": { "invoice_number": "INV-001", "customer_name": "Acme Corp" }
}'{
"success": true,
"pdf_base64": "JVBERi0x...",
"watermarked": true
}| Use case | Endpoint | Watermark | Uses credit |
|---|---|---|---|
| Draft unsaved template (design loop) | POST /api/templates/preview | No | No |
| Preview saved template with real data | POST /api/templates/:id/preview | Yes | No |
| Production PDF for end users | POST /api/pdf/token → GET /api/pdf/:token | No | Yes |
The data object uses the same variable names as production ({{customer_name}} in HTML templates, pdfme field names, etc.).
Send your data and template ID to generate a single-use token. This operation is non-blocking and instantaneous.
curl -X POST https://pdfex.io/api/pdf/token \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{
"template_id": "651...",
"data": { "name": "Alice" }
}'{
"success": true,
"token": "65eb...",
"url": "/api/pdf/65eb..."
}Use the returned URL or Token to fetch the generated PDF file.
curl -O https://pdfex.io/api/pdf/65eb...PDF links expire 15 minutes after the token is issued.
Create and manage PDF templates programmatically — ideal for automation and AI agents: draft a template, preview it, fix validation errors, save it, then generate PDFs on demand.
HTML templates use Handlebars placeholders, rendered by headless Chromium. Write ordinary HTML + CSS; use {{variable}} for data and {{#each items}}...{{/each}} for repeating rows.
data: URIs.@page and page-break-* for pagination./api/templates{
"name": "Invoice",
"engine": "html",
"html_template": "<html><head><style>body{font-family:Helvetica,Arial,sans-serif;margin:40px} h1{color:#334} table{width:100%;border-collapse:collapse} td,th{border-bottom:1px solid #ddd;padding:8px}</style></head><body><h1>Invoice {{invoice_number}}</h1><p>Billed to: {{customer_name}}</p><table><tr><th>Item</th><th>Amount</th></tr>{{#each items}}<tr><td>{{description}}</td><td>{{amount}}</td></tr>{{/each}}</table><h2>Total: {{total}}</h2></body></html>",
"sample_data": {
"invoice_number": "INV-001",
"customer_name": "Acme Corp",
"items": [{ "description": "Widget", "amount": "$100.00" }],
"total": "$100.00"
}
}/api/templates/preview{
"engine": "html",
"html_template": "<html><body><h1>Hello {{name}}</h1></body></html>",
"sampleData": { "name": "World" }
}pdfme templates place fields at exact positions on the page. All units are millimeters.
{
"basePdf": { "width": 215.9, "height": 279.4, "padding": [0, 0, 0, 0] },
"schemas": [
[
{ "name": "title", "type": "text", "position": { "x": 20, "y": 20 }, "width": 170, "height": 12, "fontSize": 24, "alignment": "center" },
{ "name": "body", "type": "text", "position": { "x": 20, "y": 40 }, "width": 170, "height": 60, "fontSize": 11 }
]
]
}basePdf — page dimensions in mm. A4 = 210×297, Letter = 215.9×279.4, Legal = 215.9×355.6. Omit to default to A4.schemas — array of pages; each page is an array of fields with unique name, type, position, width, height.text, image, svg, line, rectangle, ellipse, table.text — fontSize (pt), alignment, fontColor (hex).image — value is a data:image/... URI or http(s) URL (fetched server-side).table — value is an array of row arrays; set head and headWidthPercentages on the field./api/templates{
"name": "Certificate",
"engine": "pdfme",
"pdfme_schema": {
"basePdf": { "width": 297, "height": 210, "padding": [0, 0, 0, 0] },
"schemas": [[
{ "name": "recipient", "type": "text", "position": { "x": 48.5, "y": 80 }, "width": 200, "height": 15, "fontSize": 28, "alignment": "center" },
{ "name": "date", "type": "text", "position": { "x": 48.5, "y": 150 }, "width": 200, "height": 10, "fontSize": 12, "alignment": "center" }
]]
},
"sample_data": { "recipient": "Jane Doe", "date": "June 10, 2026" }
}For export/import via the dashboard, see Export & Import Templates above. The API uses the same bundle format with GET /api/templates/:id/export and POST /api/templates/import.
schemas[0][2] ("logo") has unsupported type "qrcode"Only the listed field types are enabled.
schemas[0][1] requires position {x: number, y: number} in mmEvery pdfme field needs numeric position and size.
html_template failed to compile: ...Malformed Handlebars syntax (e.g. unclosed {{#each}}).
HTTP 401 / 404Missing or revoked API key; or template not found / not visible to your key.