PR
PDFRender.io
HTML to PDF API for Templates, Invoices & Automated Workflows
Developer Documentation

API Reference

Convert any URL or HTML into a PDF, PNG, JPEG or WebP. Store files in your own S3 bucket, build reusable templates, and control every aspect of delivery from a single JSON API.

Bearer
Paste your key to auto-fill all code examples on this page

Introduction v1

The PDFRender.io API lets you convert any public URL or raw HTML into a PDF, PNG, JPEG or WebP. Build reusable templates, store files in your own S3 bucket, add watermarks, password-protect documents, and control every aspect of delivery from a single endpoint.

Base URL
https://pdfrender.io/api
3
Source types
4
Output formats
6
Plans available

Authentication

Every request must include your API key as a Bearer token in the Authorization header. Generate and manage keys from your API Keys page. Keep keys secret — never expose them in client-side code.

Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Security note: API keys carry full account access. Do not commit them to version control or expose them in frontend JavaScript. Use environment variables or a secrets manager.

GET Ping

Verify your API key is valid and check your current usage and plan limits.

GET /api/v1/ping
curl -X GET \
  https://pdfrender.io/api/ping \
  -H "Authorization: Bearer YOUR_API_KEY"
200 OK
{
  "ok":          true,
  "message":     "pong",
  "plan":        "medium",
  "calls_used":  142,
  "calls_limit": 5000,
  "formats":     ["pdf", "png", "jpeg", "webp"],
  "version":     "v1"
}

GET Me

Returns the account details associated with the API key.

GET /api/v1/me
curl -X GET \
  https://pdfrender.io/api/me \
  -H "Authorization: Bearer YOUR_API_KEY"
200 OK
{
  "ok": true,
  "user": {
    "id":              1,
    "email":           "you@example.com",
    "full_name":       "Joe Blogs",
    "plan":            "medium",
    "api_call_count":  142,
    "created_at":      "2026-01-01 09:00:00"
  }
}

POST Generate

Generate a PDF, PNG, JPEG or WebP from a URL, raw HTML, or a saved template. Control page layout, output format, watermarks, password protection, and where the file is delivered.

POST /api/v1/generate
Full Payload Structure
{
  "source": {
    "url":         "https://example.com",   // — or —
    "html":        "<html>...</html>",     // — or —
    "template_id": 1,                       // provide exactly one
    "data":        { },                     // required with template_id or html variables
    "baseUrl":     "https://yourdomain.com" // optional, for relative assets in html mode
  },
  "format": "pdf",                          // pdf | png | jpeg | webp
  "options": {
    "page": {
      "size":             "A4",        // named size — ignored if width/height set
      "width":            "80mm",      // custom width (overrides size)
      "height":           "50mm",      // custom height (overrides size)
      "landscape":        false,
      "margin":           "15mm",           // sets all four sides
      "marginTop":        "20mm",           // overrides margin for one side
      "marginRight":      "15mm",
      "marginBottom":     "20mm",
      "marginLeft":       "15mm",
      "scale":            1.0,
      "ranges":           "",               // e.g. "1-3" or "1,3,5"
      "preferCSSPageSize":false
    },
    "render": {
      "printBackground": true,
      "waitFor":         "#selector",       // wait for element before capture
      "timeout":         30000              // ms
    },
    "watermark": {
      "enabled":   false,
      "text":      "CONFIDENTIAL",
      "placement": "center"
    },
    "screenshot": {
      "fullPage": true,
      "width":    1280,
      "height":   800,
      "quality":  90,                       // jpeg only
      "clip": { "x": 0, "y": 0, "width": 1200, "height": 630 }
    },
    "security": {
      "password":      "user-password",     // Medium+ plan
      "ownerPassword": "owner-password"
    }
  },
  "delivery": {
    "returnType": "json",                   // json | binary
    "storage":    "none",                   // none | platform | own
    "fileName":   "my-document",
    "path":       "invoices/2026/",         // S3 path prefix
    "bucket":     "my-specific-bucket",     // S3 bucket
    "overwrite":  false
  }
}
source — Required

Provide exactly one of url, html, or template_id. Providing more than one will return a 422 error.

FieldTypeDescription
urlstringA fully qualified, publicly accessible URL to render.
htmlstringRaw HTML string. Max 2MB. Supports {{variable}} injection when data is also provided.
template_idintegerID of a saved template from your dashboard. Requires data. Large plan+.
dataobjectKey-value object of variable values to inject. Required with template_id, optional with html.
baseUrlstringBase URL for resolving relative assets in HTML mode (images, CSS, fonts).
format — Optional, default: pdf
ValueOutputNotes
pdfPDF documentDefault. All page options apply.
pngPNG imageScreenshot mode. Page options ignored. Screenshot options apply.
jpegJPEG imageScreenshot mode. quality option applies.
webpWebP imageScreenshot mode.
options.page — PDF only
FieldTypeDefaultDescription
sizestringA4Named paper size. One of: A4, A3, A5, Letter, Legal, Tabloid. Ignored if width and height are set.
widthstringCustom page width. Accepts CSS units: 80mm, 3.14in, 595px. Use with height to define a custom page size — overrides size.
heightstringCustom page height. Accepts CSS units: 50mm, 2in, 842px. Use with width to define a custom page size — overrides size.
landscapebooleanfalseRender in landscape orientation. Applies to named size only — not applicable when using custom width / height.
marginstring0mmSets all four margins. e.g. 15mm, 1in.
marginTop / Right / Bottom / LeftstringOverride individual margins. Takes precedence over margin.
scalefloat1.0Page scale between 0.1 and 2.0.
rangesstring""Page ranges to render. e.g. 1-3 or 1,3,5. Empty = all pages.
preferCSSPageSizebooleanfalseUse the page size defined in the page's CSS @page rule.
options.render
FieldTypeDefaultDescription
printBackgroundbooleantruePrint background colours and images.
waitForstringnullCSS selector to wait for before capture. Useful for pages with async/JS-rendered content. e.g. #chart-loaded
timeoutinteger30000Max wait time in milliseconds. Applies to both page load and waitFor.
options.watermark — PDF only
FieldTypeDefaultDescription
enabledbooleanfalseEnable watermark overlay.
textstring""Watermark text. e.g. CONFIDENTIAL, DRAFT, VOID.
placementstringcenterOne of: center, top-left, top-right, bottom-left, bottom-right, diagonal-full.
options.screenshot — PNG / JPEG / WebP only
FieldTypeDefaultDescription
fullPagebooleantrueCapture the full scrollable page. Set false for viewport only.
widthinteger1280Viewport width in pixels.
heightinteger800Viewport height in pixels.
qualityinteger90JPEG quality 1–100. Ignored for PNG and WebP.
clipobjectnullCapture a specific region. Object with x, y, width, height in pixels. Overrides fullPage.
options.security — PDF only · Medium+ plan

Password-protect the generated PDF using 256-bit AES encryption. The user must enter the password to open the file.

FieldTypeDefaultDescription
passwordstringnullPassword required to open the PDF.
ownerPasswordstringnullOwner password for edit/print permissions. Defaults to password if not set.
Password-protected PDFs are encrypted with high-resolution printing allowed. Modifying, copying, annotating and form-filling are disabled.
options.compliance — PDF only · Large+ plan

Generate a Factur-X / ZUGFeRD hybrid PDF — a human-readable PDF with a machine-readable EN 16931 XML invoice embedded inside. Required for EU e-invoicing mandates (Germany Jan 2025, France Sep 2026, Belgium Jan 2026 and more). Also supports PDF/A-3 conversion without XML embedding.

FieldTypeDefaultDescription
mode string none none — standard PDF (default).
pdfa3 — PDF/A-3B conversion only, no XML.
en16931 — Factur-X hybrid PDF with EN 16931 XML embedded.
extended — Factur-X hybrid with EXTENDED profile XML.
profile string en16931 XML conformance profile. en16931 or extended. Inferred from mode if not set.
xml string null Optional. Your own pre-built Factur-X XML as a base64 string. When omitted the XML is auto-generated from source.data.
When mode is en16931 or extended the API auto-generates the XML from your source.data payload. If mandatory fields are missing the request fails with a 422 listing exactly which fields are required.
delivery
FieldTypeDefaultDescription
returnType string json json returns metadata. binary streams the raw file bytes.
storage string none none — no storage (default).
platform — store in platform CDN. Medium+ plan.
own — store in your verified S3 bucket. Medium+ plan.
fileNamestringconversion-{id}Output filename without extension. Alphanumeric, hyphens and underscores only.
pathstring""Path prefix within your S3 bucket. e.g. invoices/2026/. Trailing slash required.
bucketstring""Override the bucket name set in Settings on a per-request basis. Useful when writing to multiple buckets with the same credentials. Only applies when storage: "own".
overwritebooleanfalseWhen false and a file with the same name exists, the conversion ID is auto-appended to keep the filename unique.
Examples
curl -X POST \
  https://pdfrender.io/api/generate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": { "url": "https://example.com" },
    "format": "pdf",
    "options": {
      "page":   { "size": "A4", "margin": "15mm" },
      "render": { "printBackground": true }
    },
    "delivery": { "returnType": "binary" }
  }' \
  --output document.pdf

Responses

All responses are JSON unless delivery.returnType is set to binary, in which case the raw file bytes are streamed directly.

200 — JSON mode
{
  "ok":                true,
  "conversion_id":     245090,
  "format":            "pdf",
  "file_name":         "invoice-2026-001.pdf",
  "file_size":         84231,
  "stored":            true,
  "cdn_url":           "https://your-bucket.sfo3.cdn.digitaloceanspaces.com/invoices/2026/invoice-2026-001.pdf",
  "storage":           "customer_bucket",
  "overwritten":       false,
  "auto_suffixed":     false,
  "return_type":       "json",
  "password_protected":false,
  "template_id":       1,
  "template_name":     "Simple Invoice"
}
200 — Binary mode headers
Content-Type:        application/pdf
Content-Disposition: attachment; filename="invoice-2026-001.pdf"
Content-Length:      84231
X-Conversion-Id:     245090
X-CDN-URL:           https://... (only present if stored)
Response fields
FieldTypeDescription
okbooleanAlways true on success.
conversion_idintegerUnique ID for this conversion. Reference for support.
formatstringOutput format used: pdf, png, jpeg or webp.
file_namestringFinal filename including extension.
file_sizeintegerFile size in bytes.
storedbooleanWhether the file was stored in a bucket.
cdn_urlstring|nullPublic CDN URL if stored, null otherwise.
storagestring|nullplatform_bucket, customer_bucket, or null.
overwrittenbooleanTrue if an existing file was overwritten.
auto_suffixedbooleanTrue if the conversion ID was appended to avoid a filename conflict.
password_protectedbooleanTrue if a password was applied to the PDF.
warningstringPresent if the file generated successfully but storage failed.

Error Codes

All errors return JSON with an error field describing what went wrong.

StatusCodeCause
401UnauthorizedMissing or invalid API key.
403ForbiddenFeature requires a higher plan (platform storage, password, templates, Factur-X compliance).
404Not FoundTemplate ID not found or doesn't belong to your account.
422UnprocessableInvalid or missing parameters, or missing mandatory Factur-X fields. Check the error and missing fields for details.
429Too Many RequestsPer-minute rate limit exceeded. Check Retry-After header.
503Service UnavailableGeneration server queue full or request timed out. Retry with backoff.
422 — Missing template variables
{
  "error":          "Missing required template variables.",
  "missing_fields": ["invoice_number", "lines"],
  "template_id":    1,
  "template_name":  "Simple Invoice"
}
429 — Rate limit exceeded
{
  "error":       "Rate limit exceeded. Too many requests per minute.",
  "limit":       5,
  "plan":        "small",
  "retry_after": 47,
  "upgrade_url": "https://yourdomain.com/app/settings/?tab=billing"
}
503 — Queue full
{
  "error":       "Server is busy. Please retry in a moment.",
  "code":        "QUEUE_FULL",
  "queue_size":  50,
  "retry_after": 30
}

Platform CDN Medium+ plan

Store generated files on our managed CDN and receive a permanent public URL in the response. Files are stored in your account's private folder and served over HTTPS.

curl -X POST \
  https://pdfrender.io/api/generate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source":   { "url": "https://example.com" },
    "format":   "pdf",
    "delivery": {
      "returnType": "json",
      "storage":    "platform",
      "fileName":   "contract-001",
      "overwrite":  false
    }
  }'

Own S3 Bucket Medium+ plan

Send generated files directly to your own S3-compatible bucket (AWS, DigitalOcean Spaces, Cloudflare R2 etc.). Configure and verify your bucket once in Settings → Storage, then pass delivery.storage: "own" on any request.

Setup: Go to Settings → Storage, enter your bucket credentials and click Verify. We perform a test upload and delete to confirm access before enabling.
Multiple buckets? Use a single access key with permissions across all your buckets — in DigitalOcean go to API → Spaces access keys → Generate new key (one key covers all Spaces). For AWS, create one IAM user with s3:PutObject / s3:GetObject / s3:DeleteObject permissions across the buckets you need. Then use the bucket field per-request to route to different buckets.
curl -X POST \
  https://pdfrender.io/api/generate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source":   { "url": "https://example.com/invoice/1001" },
    "format":   "pdf",
    "delivery": {
      "returnType": "json",
      "storage":    "own",
      "fileName":   "invoice-1001",
      "path":       "invoices/2026/april/",
      "overwrite":  false
    }
  }'
Routing to multiple buckets

Use the optional bucket field to override the default bucket set in Settings on a per-request basis. All requests use the same credentials — just the destination bucket changes.

## Default bucket (from Settings)
curl -X POST https://pdfrender.io/api/generate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source":   { "url": "https://example.com/invoice/1001" },
    "format":   "pdf",
    "delivery": {
      "returnType": "json",
      "storage":    "own",
      "fileName":   "invoice-1001.pdf"
    }
  }'

## Override to a different bucket
curl -X POST https://pdfrender.io/api/generate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source":   { "url": "https://example.com/invoice/1001" },
    "format":   "pdf",
    "delivery": {
      "returnType": "json",
      "storage":    "own",
      "fileName":   "invoice-1001.pdf",
      "bucket":     "my-invoices-bucket"
    }
  }'
Path behaviour
ScenarioResulting path
No path setpdfs/{year}/{month}/invoice-1001.pdf
path: "invoices/2026/"invoices/2026/invoice-1001.pdf
bucket: "other-bucket"other-bucket/pdfs/{year}/{month}/invoice-1001.pdf
Conflict + overwrite: falseinvoices/2026/invoice-1001_245091.pdf
Conflict + overwrite: trueinvoices/2026/invoice-1001.pdf

Factur-X & ZUGFeRD Compliance Large+ plan

Generate hybrid PDFs that are both human-readable and machine-readable — a standard visual PDF with a structured EN 16931 XML invoice embedded inside. Required for EU e-invoicing mandates across Germany, France, Belgium, Poland and more. Compatible with all major ERP systems including Microsoft Dynamics 365 Business Central, SAP, and Sage.

Factur-X 1.0

French/EU standard. Same XML schema as ZUGFeRD. Accepted across all EU markets.

ZUGFeRD 2.3

German standard. Identical XML to Factur-X. Accepted by DATEV, SAP, Lexware.

EN 16931

The underlying EU semantic data model. Both Factur-X and ZUGFeRD comply with this standard.

Compliance modes
ModeOutputXML embeddedPlan
noneStandard PDFNoAll plans
pdfa3PDF/A-3B compliant PDFNoLarge+
en16931Factur-X hybrid PDF/A-3Yes — EN 16931Large+
extendedFactur-X hybrid PDF/A-3Yes — EXTENDEDLarge+
Required fields in source.data — EN 16931

These fields must be present in source.data when using mode: en16931 or mode: extended. Missing fields cause a hard 422 failure listing exactly which are absent.

FieldTypeExampleNotes
invoice_number string "INV-2026-001" Unique document identifier
invoice_date string "2026-04-05" ISO date or any parseable format
currency string "GBP" ISO 4217 currency code
tax_rate number 20 Default VAT rate as a percentage
seller_name string "Acme Ltd"
seller_country string "GB" ISO 3166-1 alpha-2
seller_vat string "GB123456789" VAT registration number
buyer_name string "Customer Corp"
buyer_country string "DE" ISO 3166-1 alpha-2
lines array [{...}] Each item needs description, quantity, unit_price
Optional — EN 16931 & EXTENDED
FieldDescription
seller_address, seller_city, seller_postcode Full seller postal address. Required for EXTENDED profile.
seller_email, seller_phone Seller contact details
seller_trading_name Trading name if different from legal name
seller_tax_number Seller tax / fiscal number (FC scheme)
seller_id, seller_global_id Internal or GLN identifier for the seller party
buyer_vat Required for B2B cross-border EU transactions
buyer_address, buyer_city, buyer_postcode Full buyer postal address. Required for EXTENDED profile.
buyer_email, buyer_phone Buyer contact details
buyer_id, buyer_global_id Internal or GLN identifier for the buyer party
buyer_reference Buyer accounting or routing reference
payment_due_date Due date for payment — strongly recommended. Required for EXTENDED.
iban, bic Seller bank account for SEPA credit transfer (payment means type 58)
account_name Bank account holder name (EXTENDED)
creditor_reference SEPA direct debit creditor reference (EXTENDED)
payment_reference Payment reference / remittance info
payment_terms Human-readable payment terms text
order_number Buyer purchase order reference
seller_order_number Seller's own order/sales order number (EXTENDED)
contract_number Contract reference number
project_number, project_name Project reference for procurement tracking (EXTENDED)
delivery_date Actual delivery date at header level
invoice_period_start, invoice_period_end Billing period start and end dates (EXTENDED)
tax_point_date VAT tax point date if different from invoice date (EXTENDED)
tax_currency Tax reporting currency if different from invoice currency (EXTENDED)
tax_exemption_reason Reason text for zero-rated or exempt VAT
notes Invoice footer note or general annotation
document_type invoice (default) or credit_note — sets TypeCode to 380 or 381
document_name Custom document title
prepaid_amount Amount already paid — deducted from DuePayableAmount
amount_due Override the calculated due amount
rounding_amount Rounding adjustment applied to grand total (EXTENDED)
accounting_reference Header-level GL account code (EXTENDED)
business_process Business process identifier in document context (EXTENDED)
shipto_name, shipto_address, shipto_city, shipto_country Ship-to delivery party (EXTENDED)
despatch_advice_number Despatch advice document reference (EXTENDED)
receiving_advice_number Receiving advice document reference (EXTENDED)
card_last4, card_holder_name Card payment means details (EXTENDED, type 48)
allowances[] Header-level discounts. Each: { amount, reason, reason_code, tax_rate, percentage } (EXTENDED)
charges[] Header-level surcharges. Each: { amount, reason, reason_code, tax_rate } (EXTENDED)
lines[].tax_rate Per-line VAT rate — overrides top-level tax_rate
lines[].unit UN/ECE unit code e.g. C62 (piece), HUR (hour), KGM (kg)
lines[].sku Seller-assigned product/SKU identifier
lines[].buyer_ref Buyer-assigned product reference
lines[].global_id GTIN or other global product identifier (EXTENDED)
lines[].description_long Extended product description
lines[].origin_country Country of origin for the product (EXTENDED)
lines[].gross_price Gross unit price before discount (EXTENDED)
lines[].discount Unit price discount amount from gross to net (EXTENDED)
lines[].delivery_date Per-line delivery date (EXTENDED)
lines[].buyer_order_ref Buyer order line reference
lines[].note Per-line note or annotation
lines[].allowances[] Per-line allowances. Each: { amount, reason } (EXTENDED)
lines[].line_reference GL account code for this line (EXTENDED)
curl -X POST \
  https://pdfrender.io/api/generate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "template_id": 1,
      "data": {
        "invoice_number":   "INV-2026-001",
        "invoice_date":     "2026-04-05",
        "payment_due_date": "2026-05-05",
        "currency":         "GBP",
        "tax_rate":         20,
        "seller_name":      "Acme Ltd",
        "seller_address":   "123 Main Street",
        "seller_city":      "London",
        "seller_postcode":  "EC1A 1BB",
        "seller_country":   "GB",
        "seller_vat":       "GB123456789",
        "seller_email":     "billing@acme.com",
        "iban":             "GB29NWBK60161331926819",
        "bic":              "NWBKGB2L",
        "buyer_name":       "Customer Corp",
        "buyer_address":    "456 High Street",
        "buyer_city":       "Manchester",
        "buyer_postcode":   "M1 1AA",
        "buyer_country":    "GB",
        "buyer_vat":        "GB987654321",
        "lines": [
          { "description": "Product A", "quantity": 10, "unit_price": 49.99 },
          { "description": "Service B", "quantity": 2,  "unit_price": 150.00 }
        ],
        "notes": "Payment due within 30 days."
      }
    },
    "format": "pdf",
    "options": {
      "page":   { "size": "A4", "margin": "15mm" },
      "render": { "printBackground": true },
      "compliance": { "mode": "en16931" }
    },
    "delivery": {
      "returnType": "binary",
      "fileName":   "INV-2026-001"
    }
  }' \
  --output invoice-facturx.pdf
Missing fields — 422 response
422 — Missing mandatory compliance fields
{
  "error":      "Missing mandatory fields for EN16931 compliance.",
  "missing":    ["seller_vat", "buyer_country", "payment_due_date"],
  "compliance": "en16931",
  "help":       "Ensure all mandatory fields for EN16931 are present in source.data."
}
EU e-invoicing mandate timeline
CountryB2GB2B — receiveB2B — send
Germany 2020 Jan 2025 Jan 2027
France 2020 Sep 2026 Sep 2027 (SME)
Belgium Active Jan 2026 Jan 2026
Poland Active Feb 2026 Feb 2026
Italy Active Active Active
Romania Active Jan 2024 Jul 2024
Spain 2015 2025 2025
UK Voluntary Voluntary (PEPPOL)
EN 16931 / Factur-X covers all of the above markets. The embedded XML satisfies machine-readable requirements while the PDF satisfies human-readable requirements — both in a single file.

POST Validate PDF Large+ plan

Validate any PDF for Factur-X / ZUGFeRD and PDF/A-3 structural compliance. Pass either a public HTTPS URL or a base64-encoded PDF. Returns a detailed report of which compliance rules passed and failed.

POST /api/v1/validate
Request fields
FieldTypeRequiredDescription
pdf_base64 string Optional* The PDF file as a base64-encoded string. Max 20MB.
url string Optional* A public https:// URL to fetch and validate. Must return a PDF.
profile string Optional Compliance profile to validate against.
en16931 — Factur-X EN 16931 (default)
extended — Factur-X EXTENDED
pdfa3 — PDF/A-3B structure only

* Provide either pdf_base64 or url — not both.

# Validate a local PDF file
curl -X POST \
  https://pdfrender.io/api/validate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"pdf_base64\": \"$(base64 -i invoice.pdf)\",
    \"profile\":    \"en16931\"
  }"
Response
200 — Compliant
{
  "ok":             true,
  "compliant":      true,
  "profile":        "en16931",
  "file_size":      18547,
  "page_count":     1,
  "rules_passed":   9,
  "rules_failed":   0,
  "passed": [
    "PDF/A-3 OutputIntent present",
    "XMP Metadata stream present",
    "PDF/A-3 XMP identifier present",
    "EmbeddedFiles present: factur-x.xml",
    "AF array present in document catalog",
    "AFRelationship = /Alternative found",
    "factur-x.xml found in EmbeddedFiles",
    "Factur-X XMP present — level: EN 16931",
    "PDF has 1 page(s)"
  ],
  "failures":       [],
  "fx_level":       "EN 16931",
  "fx_filename":    "factur-x.xml",
  "embedded_files": ["factur-x.xml"],
  "validator":      "pikepdf-structural"
}
200 — Not compliant
{
  "ok":           true,
  "compliant":    false,
  "rules_passed": 6,
  "rules_failed": 3,
  "passed":       [ "..." ],
  "failures": [
    {
      "rule":        "factur-x.xml",
      "description": "factur-x.xml not found in EmbeddedFiles. Found: none"
    },
    {
      "rule":        "AFRelationship",
      "description": "No embedded file with AFRelationship=/Alternative found"
    },
    {
      "rule":        "Factur-X XMP",
      "description": "Factur-X extension schema not found in XMP metadata"
    }
  ],
  "fx_level":     null,
  "fx_filename":  null
}
Response fields
FieldTypeDescription
compliantbooleanWhether the PDF passed all compliance checks.
rules_passedintegerNumber of structural rules that passed.
rules_failedintegerNumber of structural rules that failed.
passedarrayList of passing rule descriptions.
failuresarrayList of failed rules with rule and description per item.
fx_levelstring|nullFactur-X conformance level found in XMP. e.g. EN 16931.
fx_filenamestring|nullEmbedded XML filename declared in XMP. e.g. factur-x.xml.
embedded_filesarrayAll filenames found in the EmbeddedFiles name tree.
page_countintegerNumber of pages in the PDF.
file_sizeintegerFile size in bytes.
validatorstringAlways pikepdf-structural — structural validation of PDF/A-3 markers, EmbeddedFiles, AF array and Factur-X XMP.
The validator always returns HTTP 200 — use the compliant boolean to determine pass/fail. A non-compliant result is not an API error, it is a report.

Webhook Delivery Medium+ plan

Fire-and-forget PDF generation. Submit a request and receive the result via HTTP callback when the file is ready — no need to keep the connection open. Ideal for batch workflows, server-to-server integrations, and any environment where long HTTP connections are unreliable.

Immediate response

Your request returns HTTP 202 with a job ID in milliseconds — no waiting for rendering.

HMAC signed

Every webhook delivery includes an X-PDFRender-Signature header so you can verify authenticity.

Auto retry

3 attempts with backoff (0s, 5min, 30min) if your endpoint is temporarily unavailable.

POST /api/v1/generate with returnType: webhook
delivery — webhook fields
FieldTypeRequiredDescription
returnType string Required Set to webhook to enable async delivery.
webhookUrl string Required Your public https:// endpoint that will receive the POST when the PDF is ready.
webhookSecret string Optional A secret used to sign the webhook payload. If set, an X-PDFRender-Signature header is included so you can verify delivery authenticity.
storage string Required Must be platform or own. The webhook payload includes a CDN URL — storage is required to produce one.
fileName string Optional Output filename without extension. Included in the webhook payload.
path string Optional Storage path prefix. e.g. invoices/2026/. Trailing slash required.
# Fire and forget — returns job_id immediately
curl -X POST \
  https://pdfrender.io/api/generate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "template_id": 1,
      "data": {
        "invoice_number": "INV-2026-001",
        "customer_name":  "Acme Corp"
      }
    },
    "format": "pdf",
    "options": {
      "page":   { "size": "A4", "margin": "15mm" },
      "render": { "printBackground": true }
    },
    "delivery": {
      "returnType":    "webhook",
      "webhookUrl":    "https://yourapp.com/pdf-ready",
      "webhookSecret": "your-signing-secret",
      "storage":       "own",
      "fileName":      "INV-2026-001",
      "path":          "invoices/2026/"
    }
  }'
Immediate response — HTTP 202
202 Accepted
{
  "ok":          true,
  "job_id":      "job_a3f9k2x8m1",
  "status":      "queued",
  "webhook_url": "https://yourapp.com/pdf-ready",
  "message":     "Job queued. Your webhook will be called when the PDF is ready.",
  "status_url":  "https://pdfrender.io/api/v1/jobs/job_a3f9k2x8m1"
}
Webhook delivery — payload

When the PDF is ready your endpoint receives a POST request with the following JSON body and headers.

HeaderDescription
Content-Typeapplication/json
X-PDFRender-Job-IdThe job ID
X-PDFRender-TimestampUnix timestamp of delivery
X-PDFRender-Signaturesha256=HMAC of the request body — only present if webhookSecret was set
POST to your webhookUrl
{
  "job_id":       "job_a3f9k2x8m1",
  "ok":           true,
  "status":       "complete",
  "format":       "pdf",
  "file_name":    "INV-2026-001.pdf",
  "file_size":    84231,
  "cdn_url":      "https://your-bucket.nyc3.cdn.digitaloceanspaces.com/invoices/2026/INV-2026-001.pdf",
  "storage":      "customer_bucket",
  "render_ms":    2341,
  "delivered_at": "2026-04-06T12:00:03+00:00"
}
Your endpoint must return a 2xx response within 15 seconds. Non-2xx responses trigger a retry. Return 200 OK as quickly as possible and process the PDF asynchronously if needed.
Signature verification

If you set a webhookSecret, always verify the X-PDFRender-Signature header before processing the payload. The signature is computed as sha256=HMAC-SHA256(rawBody, secret). Use a timing-safe comparison to prevent timing attacks.

$body      = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PDFRENDER_SIGNATURE'] ?? '';
$expected  = 'sha256=' . hash_hmac('sha256', $body, 'your-signing-secret');

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

$data = json_decode($body, true);
// PDF is ready at: $data['cdn_url']
Retry behaviour
AttemptDelay after failureNotes
1ImmediateFirst attempt — next available cron run
25 minutesTriggered if attempt 1 returns non-2xx or times out
330 minutesFinal attempt — job marked failed if this also fails
After all 3 attempts are exhausted the job status becomes failed. Poll GET /api/v1/jobs/{job_id} to inspect the error and retrieve any partial result. Jobs expire after 7 days.

GET Job Status Medium+ plan

Poll for the status of an async job. Useful as a fallback if your webhook endpoint missed a delivery, or to confirm completion before taking action.

GET /api/v1/jobs/{job_id}
curl -X GET \
  https://pdfrender.io/api/jobs/job_a3f9k2x8m1 \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
200 — Job found
{
  "ok":           true,
  "job_id":       "job_a3f9k2x8m1",
  "status":       "complete",
  "webhook_url":  "https://yourapp.com/pdf-ready",
  "attempts":     1,
  "created_at":   "2026-04-06 12:00:00",
  "completed_at": "2026-04-06 12:00:03",
  "expires_at":   "2026-04-13 12:00:00",
  "cdn_url":      "https://your-bucket.nyc3.cdn.digitaloceanspaces.com/invoices/2026/INV-2026-001.pdf",
  "file_name":    "INV-2026-001.pdf",
  "file_size":    84231,
  "render_ms":    2341,
  "error":        null
}
Status values
StatusMeaning
queuedWaiting to be picked up — usually less than 60 seconds
processingCurrently being generated by the render engine
completePDF generated, stored, and webhook delivered successfully
failedAll 3 delivery attempts exhausted — check the error field
Jobs are accessible for 7 days after creation. After that the record is deleted and the endpoint returns 404.

Templates Large+ plan

Build reusable PDF layouts once in the visual template builder, declare your variables, and generate personalised documents on demand by passing a JSON data payload. No HTML required on your side.

1
Build

Design your layout in the drag-and-drop builder

2
Define

Declare variables — strings, numbers, arrays

3
Call

POST with template_id and a data object

4
Receive

Get back a PDF — same as any other generate call

Template Builder

The builder is available at Templates in your dashboard. It uses a section → row → column → block model.

Left panel — Blocks

Drag layout blocks (1 col, 2 col, 60/40 etc.) onto the canvas, then drag content blocks (Text, Table, Totals, Image, QR Code, HTML) into columns.

Right panel — Properties

Click any block to edit its properties — font, colour, alignment, table columns, computed formulas. Switch to Variables to manage variables. Switch to Page for paper size and footer.

Auto-save

The builder saves 3 seconds after your last change. Click Save at any time. Use Preview to see the template rendered with sample data.

Block Types

BlockPurposeVariable support
text Paragraph, heading, label or any static or dynamic text ✓ {{variable}}
table Repeating rows from an array variable ✓ dataKey maps to array
totals Auto-computed subtotal, tax and grand total rows ✓ computed expressions
image Static or dynamic image from a URL or variable ✓ {{logo_url}}
divider Horizontal separator line
spacer Empty vertical space
qrcode QR code from a value or variable ✓ {{booking_ref}}
html Raw HTML block for complex layouts ✓ {{variable}} in HTML

Variables

Declare variables in the Variables tab of the builder. Use {{variable_key}} inside Text or HTML blocks. The value is injected at render time from your source.data object.

TypeUsageExample valueExample in template
stringNames, dates, addresses, notes"Joe Blogs"{{customer_name}}
numberUsed in computed expressions20{{tax_rate}}% or in: subtotal * (tax_rate / 100)
arrayRepeating rows for Table and Totals blocks[{description, quantity, unit_price}]Table dataKey: "lines"
Variables marked Required in the builder must be present in source.data or the API returns a 422 listing the missing fields. Optional variables with a default use the default when omitted.

Template API Usage

Find your template ID in the builder URL: /app/templates/builder/?id=12. Pass it as source.template_id alongside your data.

curl -X POST \
  https://pdfrender.io/api/generate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "template_id": 1,
      "data": {
        "company_name":    "Acme Ltd",
        "company_email":   "billing@acme.com",
        "company_address": "123 Main Street, London",
        "invoice_number":  "INV-2026-001",
        "invoice_date":    "3 April 2026",
        "due_date":        "3 May 2026",
        "customer_name":   "Joe Blogs",
        "customer_email":  "josh@example.com",
        "tax_rate":        20,
        "notes":           "Thank you for your business.",
        "lines": [
          { "description": "LED Strip EF160", "quantity": 10, "unit_price": 49.99 },
          { "description": "Installation",    "quantity": 2,  "unit_price": 150.00 }
        ]
      }
    },
    "format":   "pdf",
    "delivery": { "returnType": "binary", "fileName": "invoice-INV-2026-001" }
  }' \
  --output invoice.pdf

Starter Templates

Your account includes ready-made templates. Duplicate and customise them in the builder, or use them as-is straight away.

IDNameCategoryKey variables
1 Simple Invoice invoice company_name, customer_name, invoice_number, lines[], tax_rate
2 Simple Receipt receipt company_name, receipt_number, items[], tax_rate, payment_method
3 Formal Letter letter sender_name, recipient_name, subject, body_paragraph_1
4 Boarding Pass ticket airline_name, flight_number, departure_code, arrival_code, passenger_name, seat, booking_ref
5 Business Report report company_name, report_title, executive_summary, rows[], metric_1_label, metric_1_value
6 Purchase Order contract company_name, po_number, vendor_name, items[], tax_rate
Open the template in the builder — the ID is in the URL: /app/templates/builder/?id=12. The number after id= is your template ID.
Yes — all storage options and password protection work with template-generated PDFs exactly as with URL-based generation.
No. Each API call renders the template at the time of the request. Previously generated PDFs are unaffected. Call the API again with the same data to regenerate.
Yes — text blocks can mix static and dynamic content freely. For example: Invoice #{{invoice_number}} — due {{due_date}}.
Yes — undeclared fields are ignored. Only fields referenced as {{variable}} in the template blocks are used.

Plan Limits

All plans include the core generate endpoint. Higher plans unlock storage, password protection, templates and priority queue processing.

Plan Calls / mo Rate / min Priority CDN Storage Own S3 Password Templates Factur-X
Free 100 2 0
Small 1,500 5 1
Medium 5,000 15 2
Large 25,000 30 3
xLarge 50,000 60 4
Enterprise Custom 120 5
Monthly call limits reset on the 1st of each month. Enterprise plans use a custom limit set per account — contact us to discuss your requirements.

Rate Limits

In addition to monthly call limits, each plan has a per-minute rate limit. Exceeding it returns a 429 response with a Retry-After header indicating how many seconds to wait before retrying.

429 — Rate limit exceeded
{
  "error":       "Rate limit exceeded. Too many requests per minute.",
  "limit":       5,
  "plan":        "small",
  "retry_after": 47,
  "upgrade_url": "https://yourdomain.com/app/settings/?tab=billing"
}
Priority queue: When the server is under load, higher-plan requests are processed first. Enterprise (priority 5) always jumps ahead of Free (priority 0) in the queue. Queue position does not affect your rate limit — it only affects how quickly a slot becomes available.

Usage Notifications

We send email alerts when your monthly API call usage reaches 50%, 80% and 100% of your plan limit. This gives you time to upgrade before hitting your limit mid-month.

50%

First warning — plenty of headroom remaining

80%

Second warning — consider upgrading soon

100%

Final warning — API calls will be rejected until reset or upgrade

Notifications are sent to the email address registered on your account. Upgrade your plan at any time from Settings → Billing.