Templates API
List your templates, generate and merge PDFs, render raw HTML, and check your usage.
Base URL: https://pdfstork.com/api/v1
All endpoints require an Authorization: Bearer header. See Authentication. Each PDF costs one credit; see Rate Limits & Quotas.
List templates
GET /api/v1/templatesReturns the templates of the token's team, 50 per page. Pass ?page=2 to get the next page. The type is editor for a template made with the WYSIWYG editor and html for an HTML template.
curl https://pdfstork.com/api/v1/templates \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json"{
"data": [
{
"id": 123,
"name": "Invoice",
"type": "editor",
"has_base_pdf": true,
"created_at": "2026-03-15T10:00:00+00:00",
"updated_at": "2026-03-28T14:30:00+00:00"
},
{
"id": 456,
"name": "Contract",
"type": "html",
"has_base_pdf": false,
"created_at": "2026-03-20T09:00:00+00:00",
"updated_at": "2026-03-20T09:00:00+00:00"
}
],
"links": {
"first": "https://pdfstork.com/api/v1/templates?page=1",
"last": "https://pdfstork.com/api/v1/templates?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 50,
"total": 2
}
}Get a template
GET /api/v1/templates/{template_id}Returns one template, with the same fields as the list.
{
"data": {
"id": 123,
"name": "Invoice",
"type": "editor",
"has_base_pdf": true,
"created_at": "2026-03-15T10:00:00+00:00",
"updated_at": "2026-03-28T14:30:00+00:00"
}
}Generate PDF
POST /api/v1/templates/{template_id}/generateGenerates a PDF from the published version of the template, injecting the provided data. Costs one credit.
Request body
The body must be a JSON object with a data object. An empty body is refused with a 422 (The request body is empty…), and so is a data that is not an object (The data field must be an object.).
| Field | Type | Default | Description |
|---|---|---|---|
data | object | {} | Key-value pairs for template variables |
output | string | "base64" | base64, url or binary |
filename | string | "document.pdf" | Output filename. Letters, numbers, dashes, underscores and dots only |
url_expiry | integer | 60 | URL validity in minutes, from 1 to 1440. Only used with output: "url". A value outside this range is refused with a 422 |
metadata | object | None | Document properties to write into the PDF: title, author, subject, keywords, creator. Each overrides the template's own value and may use {{ variables }}. Basic plan and above |
curl -X POST https://pdfstork.com/api/v1/templates/123/generate \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"data": {
"customer_name": "Acme Corp",
"invoice_number": "INV-2026-042"
},
"output": "base64",
"filename": "invoice-042.pdf"
}'Response (base64)
The usage object already counts the PDF you just generated. percentage is the share of the monthly quota used, from 0 to 100, and pack_credits the prepaid credits left.
{
"success": true,
"pdf": "JVBERi0xLjQKJeLj...",
"filename": "invoice-042.pdf",
"size": 12345,
"usage": {
"current": 42,
"limit": 5000,
"percentage": 0.8,
"pack_credits": 0
}
}Response (url)
{
"success": true,
"url": "https://de-s3.storage.bunnycdn.com/pdfapi-pdfs/pdfs/abc123.pdf?X-Amz-Signature=...",
"filename": "invoice-042.pdf",
"expires_at": "2026-03-30T11:00:00+00:00",
"size": 12345,
"usage": {
"current": 42,
"limit": 5000,
"percentage": 0.8,
"pack_credits": 0
}
}Response (binary)
Returns raw PDF bytes with Content-Type: application/pdf. Ask for it with "output": "binary" in the body: an Accept: application/pdf header alone does not change the output format.
Merge PDFs
POST /api/v1/templates/mergeGenerates several documents and merges them into one PDF, in the order given. Each template can receive its own data. Up to 50 documents per request. A merge of N documents costs N credits. Pro plan only.
| Field | Type | Default | Description |
|---|---|---|---|
templates | array | Required | The documents to merge, each as {"id": 123, "data": {…}} |
output | string | "base64" | base64, url or binary |
filename | string | "document.pdf" | Output filename. Letters, numbers, dashes, underscores and dots only |
url_expiry | integer | 60 | URL validity in minutes, from 1 to 1440. Only used with output: "url". A value outside this range is refused with a 422 |
metadata | object | None | Document properties of the merged file (title, author, subject, keywords, creator). Placeholders are filled from the first document's data. Without it, the merged file keeps the properties of its first document |
curl -X POST https://pdfstork.com/api/v1/templates/merge \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"templates": [
{ "id": 123, "data": { "customer_name": "Acme Corp" } },
{ "id": 456, "data": { "customer_name": "Acme Corp" } }
],
"output": "url",
"filename": "acme-bundle.pdf"
}'The response has the same shape as the generate endpoint for the chosen output.
HTML to PDF
POST /api/v1/html-to-pdfRenders raw HTML without a saved template. The HTML may use the same Twig syntax as the HTML editor, filled from data. Costs one credit.
| Field | Type | Default | Description |
|---|---|---|---|
html | string | Required | The HTML to render |
data | object | {} | Values for the Twig variables |
config | object | {} | Page settings: format (default A4), orientation (portrait or landscape), margins (top, right, bottom, left in mm, 10 by default), header, footer (HTML) and metadata |
output | string | "base64" | base64, url or binary |
filename | string | "document.pdf" | Output filename. Letters, numbers, dashes, underscores and dots only |
url_expiry | integer | 60 | URL validity in minutes, from 1 to 1440. Only used with output: "url". A value outside this range is refused with a 422 |
curl -X POST https://pdfstork.com/api/v1/html-to-pdf \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"html": "<h1>Hello {{ name }}</h1>",
"data": { "name": "Jane Doe" },
"config": {
"format": "A4",
"orientation": "portrait",
"margins": { "top": 20, "right": 15, "bottom": 20, "left": 15 },
"footer": "<div style=\"font-size: 9px\">Page <span class=\"pageNumber\"></span> of <span class=\"totalPages\"></span></div>"
},
"output": "base64"
}'The response has the same shape as the generate endpoint for the chosen output.
Check usage
GET /api/v1/usageReturns your current monthly usage and quota information, wrapped in a data object. has_quota_remaining stays true past the quota as long as prepaid credits are left, and plan is discovery, basic or pro. daily_usage covers the last 31 days.
curl https://pdfstork.com/api/v1/usage \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json"{
"data": {
"current_month_usage": 42,
"quota_limit": 5000,
"quota_percentage": 0.8,
"pack_credits": 0,
"has_quota_remaining": true,
"plan": "basic",
"daily_usage": [
{ "date": "2026-03-28", "count": 15 },
{ "date": "2026-03-29", "count": 12 },
{ "date": "2026-03-30", "count": 15 }
],
"overage_count": 0
}
}Error responses
| Status | Description |
|---|---|
401 | Missing or invalid API token |
402 | Monthly quota and prepaid credits both used up. The body gives the usage and a purchase_url |
403 | The template belongs to another account, the account's email address is not verified, or the request uses a feature your plan does not include (the body gives required_plan) |
404 | Template not found, for example {"message": "Template 999 not found."} |
422 | Validation error (see the errors object), or a Twig error in an HTML template. No credit is consumed |
429 | Rate limit exceeded (60 requests per minute per team) |
500 | PDF generation failed |
{
"message": "The output must be one of: base64, url, binary.",
"errors": {
"output": [
"The output must be one of: base64, url, binary."
]
}
}A template whose Twig does not compile is refused before rendering. The response names the part (template, header or footer) and the line:
{
"message": "Twig error in the template, line 2: missing endif (the if opened here is never closed)",
"part": "template",
"line": 2,
"errors": {
"template": [
"Twig error in the template, line 2: missing endif (the if opened here is never closed)"
]
}
}