PDFEx

Documentation

Use the dashboard to design templates and move them between environments, or integrate PDFEx programmatically with the REST API.

Using the Dashboard

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.

Recommended workflow

  1. Create an API key at API Keys if you plan to call the REST API from another app.
  2. Create or import templates on Templates — use sample invoices/certificates, or open Design to edit visually (pdfme or HTML engine).
  3. Test a PDF with real data on Generate.
  4. Copy the template ID from the Templates page (code icon on each card) when wiring up POST /api/pdf/token or POST /api/templates/:id/preview.

Dashboard pages

PageWhat it does
/secure/templatesList templates, export/import bundles, delete, open designer
/secure/designVisual editor for pdfme layouts or HTML + Handlebars
/secure/generatePick a template, fill variables, download a test PDF
/secure/keysCreate and revoke API keys for REST access
/secure/tokensView recent PDF generation requests
/secure/upgradeView or change subscription tier and limits

Template limits by plan

TierMonthly PDF requestsMax templates
Hobby1002
Starter2,5005
Pro5,00015
Business50,00035
Max1B5,000

Export & Import Templates

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.

In the dashboard

  1. On Templates, click the download icon on a template card to export one template, or click Export All to download every template you can see.
  2. On the target environment, click Import and select your .pdfex-templates.json file.
  3. Choose Update existing to replace templates that have the same name (recommended for production rollouts), or Create new to always add copies without overwriting.
  4. After import, open Templates and note the new template IDs — they will differ from staging. Update any integrations, webhooks, or saved template_id values accordingly. API keys are not exported; create new keys on each environment.
Import counts toward your plan's template limit. If a template is skipped, check the toast message — common reasons are duplicate names (when using Create new) or reaching your max template count.

Export file format

{
  "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
    }
  ]
}

REST API (same bundle format)

/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": [] } }

Authentication

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>
0

Preview Saved Template (Optional)

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.

Endpoint

POST/api/templates/:id/preview

Request Body

  • data (object) Required — same shape as production token requests
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 Response (200 OK)

{
  "success": true,
  "pdf_base64": "JVBERi0x...",
  "watermarked": true
}

Preview vs production

Use caseEndpointWatermarkUses credit
Draft unsaved template (design loop)POST /api/templates/previewNoNo
Preview saved template with real dataPOST /api/templates/:id/previewYesNo
Production PDF for end usersPOST /api/pdf/token → GET /api/pdf/:tokenNoYes

The data object uses the same variable names as production ({{customer_name}} in HTML templates, pdfme field names, etc.).

1

Generate Token

Send your data and template ID to generate a single-use token. This operation is non-blocking and instantaneous.

Endpoint

POST/api/pdf/token

Request Body

  • template_id (string) Required
  • data (object) Required JSON data
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 Response (200 OK)

{
  "success": true,
  "token": "65eb...",
  "url": "/api/pdf/65eb..."
}
2

Retrieve PDF

Use the returned URL or Token to fetch the generated PDF file.

Endpoint

GET/api/pdf/:token
curl -O https://pdfex.io/api/pdf/65eb...

PDF links expire 15 minutes after the token is issued.

REST API Reference

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.

Endpoints

GET/api/templates
POST/api/templates
GET/api/templates/:id
PUT/api/templates/:id
DELETE/api/templates/:id
GET/api/templates/:id/export
POST/api/templates/import
POST/api/templates/preview
POST/api/templates/:id/preview
POST/api/pdf/token
GET/api/pdf/:token

The design loop

  1. Draft a template (pdfme JSON or html — see engines below).
  2. POST /api/templates/preview with sample data → returns pdf_base64.
  3. Inspect the result; fix validation errors in details.
  4. POST /api/templates to save → returns template id.
  5. POST /api/templates/:id/preview with data → watermarked pdf_base64 (no token).
  6. POST /api/pdf/token with template_id + data, then GET the returned url for a clean production PDF.

Engine: html (recommended for agents)

HTML templates use Handlebars placeholders, rendered by headless Chromium. Write ordinary HTML + CSS; use {{variable}} for data and {{#each items}}...{{/each}} for repeating rows.

  • JavaScript is disabled; external network requests are blocked.
  • Images must be inline data: URIs.
  • Default page size is US Letter; use CSS @page and page-break-* for pagination.
  • Variables are auto-extracted from placeholders — no need to list them.

Create example

/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"
  }
}

Preview example

/api/templates/preview
{
  "engine": "html",
  "html_template": "<html><body><h1>Hello {{name}}</h1></body></html>",
  "sampleData": { "name": "World" }
}

Engine: pdfme (coordinate-based)

pdfme templates place fields at exact positions on the page. All units are millimeters.

Template structure

{
  "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.
  • Supported types: text, image, svg, line, rectangle, ellipse, table.
  • textfontSize (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.

Create example

/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.

Common validation errors

  • 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 mm

    Every pdfme field needs numeric position and size.

  • html_template failed to compile: ...

    Malformed Handlebars syntax (e.g. unclosed {{#each}}).

  • HTTP 401 / 404

    Missing or revoked API key; or template not found / not visible to your key.