Quaterio

API Reference

Generate PDFs, manage documents, and configure webhooks via REST API. Available on Business plan and above.

Base URL: https://quaterio.com/api/v1

Authentication

All API requests require a Bearer token in the Authorization header. Tokens use the q_ prefix and are verified via SHA-256 hash lookup.

# Include your API token in every request
curl https://quaterio.com/api/v1/documents \
-H "Authorization: Bearer q_your_api_token"

Generate API tokens in your Dashboard → Settings → API Tokens. API access requires a Business or Enterprise subscription.

PDF Generation

Generate PDFs from templates with variable replacement, raw HTML or Markdown. All endpoints accept page settings (margins, font family, columns, headers, footers) and support returnBlob: true for binary response.

POST/api/v1/generate/template

Generate a PDF from a template with variable replacement. Supports caching, print-ready output (CMYK, trim marks) and debug mode.

Show all 6 parameters
ParameterTypeDescription
templateIdrequiredstringUUID of the template, or the custom ID you set in the dashboard (e.g. "monthly_invoice")
variablesobjectKey-value pairs for template placeholders. Supports text, html, markdown, qrcode, barcode and formfield content types. A value may also be an array of objects, which fills a repeating region in the template (see below)
returnBlobbooleanReturn binary PDF instead of download URL
printReadyobjectPrint-ready output: { cmyk, outlineFonts, trimMarks, bleedMm } (Business+ only)
noCachebooleanSkip cache and regenerate
debugbooleanInclude debug information in response
curl -X POST https://quaterio.com/api/v1/generate/template \ -H "Authorization: Bearer q_..." \ -H "Content-Type: application/json" \ -d '{ "templateId": "monthly_invoice", "variables": { "company": "Acme Inc", "total": "$4,250.00", "lines": [ { "description": "Consulting", "qty": "10", "amount": "$1,500.00" }, { "description": "Design work", "qty": "3", "amount": "$2,750.00" } ] } }' # → { "ok": true, "data": { "downloadUrl": ".../invoice_en_a7f3b2c1.pdf" } } # # "lines" fills a repeating region. In the template, mark the row that should # repeat and address each field with the collection name: # # <tr data-repeat="lines"> # <td><!-- placeholder: lines.description --></td> # <td><!-- placeholder: lines.qty --></td> # <td><!-- placeholder: lines.amount --></td> # </tr> # # The row is rendered once per entry, so the template needs no fixed row count. # An empty or omitted array removes the row entirely.
POST/api/v1/generate/html

Generate a PDF from raw HTML. Accepts page settings for margins, font family, columns, headers and footers.

Show all 12 parameters
ParameterTypeDescription
htmlrequiredstringHTML content to render as PDF
returnBlobbooleanReturn binary PDF instead of download URL
pageSizestringPage size preset: a4, letter, legal, a3, a5 (default: a4). Ignored when pageWidthMm/pageHeightMm are set
pageWidthMmnumberCustom page width in mm (50-1000). Use with pageHeightMm instead of pageSize
pageHeightMmnumberCustom page height in mm (50-2000). Use with pageWidthMm instead of pageSize
marginsobjectPage margins in mm: { top, right, bottom, left } (each 0-100)
stylesobjectTypography: { h1Size, h2Size, h3Size, h4Size, h5Size, h6Size, pSize, lineHeight }
fontFamilystringFont family for the document (any Google Font name)
columnsnumberNumber of text columns, 1-4 (default: 1)
headerobjectHeader config: { content, isVisible, alignment, horizontalAlignment }
footerobjectFooter config: { content, isVisible, alignment, horizontalAlignment }
printReadyobjectPrint-ready output: { cmyk, outlineFonts, trimMarks, bleedMm } (Business+ only)
curl -X POST https://quaterio.com/api/v1/generate/html \ -H "Authorization: Bearer q_..." \ -H "Content-Type: application/json" \ -d '{ "html": "<h1>Hello World</h1><p>Your content here.</p>", "pageSize": "a4", "margins": { "top": 20, "right": 15, "bottom": 20, "left": 15 }, "fontFamily": "Inter", "columns": 1 }'
POST/api/v1/generate/markdown

Generate a PDF from Markdown. Converted to HTML internally, then rendered with the same engine.

Show all 12 parameters
ParameterTypeDescription
markdownrequiredstringMarkdown content to render as PDF
returnBlobbooleanReturn binary PDF instead of download URL
pageSizestringPage size preset: a4, letter, legal, a3, a5 (default: a4). Ignored when pageWidthMm/pageHeightMm are set
pageWidthMmnumberCustom page width in mm (50-1000). Use with pageHeightMm instead of pageSize
pageHeightMmnumberCustom page height in mm (50-2000). Use with pageWidthMm instead of pageSize
marginsobjectPage margins in mm: { top, right, bottom, left } (each 0-100)
stylesobjectTypography: { h1Size, h2Size, h3Size, h4Size, h5Size, h6Size, pSize, lineHeight }
fontFamilystringFont family for the document (any Google Font name)
columnsnumberNumber of text columns, 1-4 (default: 1)
headerobjectHeader config: { content, isVisible, alignment, horizontalAlignment }
footerobjectFooter config: { content, isVisible, alignment, horizontalAlignment }
printReadyobjectPrint-ready output: { cmyk, outlineFonts, trimMarks, bleedMm } (Business+ only)
curl -X POST https://quaterio.com/api/v1/generate/markdown \ -H "Authorization: Bearer q_..." \ -H "Content-Type: application/json" \ -d '{ "markdown": "# Report\n\nQuarterly summary...", "pageSize": "letter", "margins": { "top": 25, "right": 20, "bottom": 25, "left": 20 }, "header": { "content": "Quarterly Report", "isVisible": true } }'
POST/api/v1/generate/batch

Generate multiple PDFs from one template. Each row provides its own set of variables. Returns per-row success/failure status.

ParameterTypeDescription
templateIdrequiredstringUUID of the template, or the custom ID you set in the dashboard (e.g. "monthly_invoice")
rowsrequiredarrayArray of variable objects. Each entry generates ONE SEPARATE PDF with its own variables. Not to be confused with a repeating region inside a single document, which is an array within one entry. Business: max 100 rows. Enterprise: max 1,000
concurrencynumberParallel generation limit (default 5, max 20)
curl -X POST https://quaterio.com/api/v1/generate/batch \ -H "Authorization: Bearer q_..." \ -d '{ "templateId": "monthly_invoice", "rows": [ { "company": "Acme Inc", "total": "$1,000" }, { "company": "Globex Corp", "total": "$2,500" } ], "concurrency": 5 }'
POST/api/v1/generate/facturx

Generate an EN 16931 electronic invoice as a PDF/A-3 with the CII XML embedded (Factur-X / ZUGFeRD). Business plan and up. Totals are derived from the lines, never accepted from the request, and the finished XML is validated against the CEN Schematron before a PDF is assembled: a failing invoice returns findings rather than a file. Takes no page settings, because templateId must be a layout already checked for the placeholders the invoice needs.

Show all 20 parameters
ParameterTypeDescription
templateIdrequiredstringA Factur-X starter (system-facturx-simple, system-facturx-simple-fr, system-facturx-simple-de, system-facturx-vat-breakdown, system-facturx-vat-breakdown-fr, system-facturx-vat-breakdown-de, system-facturx-reverse-charge, system-facturx-reverse-charge-fr, system-facturx-reverse-charge-de), or the UUID/alias of your own layout. It must declare a placeholder for all ten required business terms or the request is rejected before rendering, and the error names both the terms it wants and the ones your template has
invoicerequiredobjectThe invoice, as EN 16931 business terms. Amounts are STRINGS throughout, so no float rounding reaches a total, and no totals are accepted: every one is derived from the lines. Its fields follow
invoice.numberrequiredstringBT-1. Your invoice number, unique within your own numbering series
invoice.issueDaterequiredstringBT-2. ISO date, YYYY-MM-DD
invoice.typeCoderequiredstringBT-3. "380" for a commercial invoice, "381" for a credit note
invoice.currencyrequiredstringBT-5. Uppercase ISO 4217 code, e.g. "SEK"
invoice.sellerrequiredobjectBG-4. { name, address, vatId, tradingName, legalRegistrationId, electronicAddress }. Only name and address.country are required. The address is { country, line1, line2, city, postcode, countrySubdivision }, where country is an uppercase ISO 3166-1 alpha-2 code (Greece is GR, not EL). electronicAddress is { value, scheme }, because an address without its register is rejected by BR-62
invoice.buyerrequiredobjectBG-7. The same shape as seller
invoice.linesrequiredarrayBG-25. At least one line, at most 1,000. Each is { id, name, quantity, unitCode, netPrice, vat, description, buyerOrderLineReference }. netPrice is the price of ONE unit after any item discount: the line total is derived from it, never sent. unitCode is a UN/ECE Rec 20 code, C62 for a plain count. vat is { code, rate, exemptionReason }, where code is an EN 16931 category (S, Z, E, AE, K, G, O, L, M) and rate is a percentage as a string, so 25% is "25" and not "0.25"
invoice.targetProfilestringWhich profile to target. Only "EN16931" is generated today. A target, not a declaration: what the file actually is gets read back off BT-24 after the XML is built
invoice.dueDatestringBT-9. ISO date
invoice.deliveryDatestringBT-72. ISO date. Neither the issue date nor the tax point
invoice.buyerReferencestringBT-10. The buyer's own reference, which public sector buyers often require
invoice.notestringBT-22. A free text note on the whole invoice
invoice.allowancesarrayBG-20. Document level discounts, up to 100. Each is { amount, vat, reason, reasonCode }, and either reason or reasonCode is required (BR-33). amount is always positive: the direction comes from which array it sits in
invoice.chargesarrayBG-21. Document level charges, the same shape as allowances
invoice.paymentMeansobjectBG-16. { typeCode, accountIdentifier, bic, accountName }. typeCode 58 is a SEPA credit transfer and 30 a generic one; accountIdentifier is the IBAN, or the local account number where IBAN does not apply
invoice.paymentTermsstringBT-20. Free text, e.g. "30 days net"
invoice.prepaidAmountstringBT-113. Already paid, and subtracted from the amount due
returnBlobbooleanDefaults to TRUE here, unlike every other generate endpoint. An invoice carries the buyer identity, the amounts and the seller bank account, so the bytes come back in the response rather than being uploaded to a URL the PDF proxy serves without authentication. Send false to get a URL instead
curl -X POST https://quaterio.com/api/v1/generate/facturx \ -H "Authorization: Bearer q_..." \ -H "Content-Type: application/json" \ --output invoice.pdf \ -d '{ "templateId": "system-facturx-simple", "invoice": { "number": "2026-0042", "issueDate": "2026-08-11", "typeCode": "380", "currency": "EUR", "dueDate": "2026-09-10", "seller": { "name": "Acme AB", "vatId": "SE556677889901", "address": { "line1": "Storgatan 1", "city": "Stockholm", "postcode": "111 22", "country": "SE" } }, "buyer": { "name": "Globex SARL", "vatId": "FR12345678901", "address": { "line1": "10 rue de Rivoli", "city": "Paris", "postcode": "75004", "country": "FR" } }, "lines": [ { "id": "1", "name": "Consulting", "quantity": "10", "unitCode": "HUR", "netPrice": "99.00", "vat": { "code": "S", "rate": "20.00" } } ], "paymentTerms": "30 days net" } }'

Resources

Create, read, update and delete documents. Give a document an alias and every endpoint here accepts that name in place of its ID. All list endpoints support pagination and language filtering.

GET/api/v1/documents

List all documents in your organization. Supports pagination and language filtering.

POST/api/v1/documents

Create a new document.

ParameterTypeDescription
titlerequiredstringDocument title
languagestringLanguage code (defaults to organization default)
GET/api/v1/documents/:id

Get a single document. :id accepts the UUID or the alias.

PATCH/api/v1/documents/:id

Update a document in place. Send only the fields you want to change. Setting an alias is what makes a document callable by name from the rest of the API.

Show all 6 parameters
ParameterTypeDescription
titlestringNew title
slugstringAlias, 1-50 characters of a-z, 0-9, dash and underscore. Send null or "" to remove it. Omit the field to leave the current alias alone: renaming a document never moves its alias.
descriptionstringFree text description
languagestringLanguage code
categoriesarrayCategory names
contentobjectThe serialized layout. The variable schema is recomputed from it, so /schema stays in step.
curl -X PATCH https://quaterio.com/api/v1/documents/<id> \ -H "Authorization: Bearer q_..." \ -H "Content-Type: application/json" \ -d '{"slug": "monthly_invoice"}' # The document is now callable by name: # POST /api/v1/generate/template {"templateId": "monthly_invoice", ...}
DELETE/api/v1/documents/:id

Delete a document. :id accepts the UUID or the alias.

GET/api/v1/documents/:id/schema

Get the variable schema for a document. Returns each placeholder name, its content type (text, html, markdown, qrcode, barcode, formfield), whether a default value is set (required = no default), and the number of occurrences. Use this so agents can bind their own data without reading the document source.

curl https://quaterio.com/api/v1/documents/monthly_invoice/schema \ -H "Authorization: Bearer q_..." # → { # "ok": true, # "data": { # "documentId": "...", # "slug": "monthly_invoice", # "title": "Monthly Invoice", # "language": "en", # "updatedAt": "2026-08-07T10:12:00.000Z", # "variables": [ # { "name": "customer_name", "contentType": "text", "required": true, "defaultValue": null, "occurrences": 2 }, # { "name": "terms_html", "contentType": "html", "required": false, "defaultValue": "<p>Standard terms apply.</p>", "occurrences": 1 }, # { "name": "tracking_qr", "contentType": "qrcode", "required": true, "defaultValue": null, "occurrences": 1, "options": { "size": 150 } } # ] # } # }
POST/api/v1/documents/:id/duplicate

Duplicate a document with optional title and language override. The alias is not copied: it is unique per organization and identifies the original.

ParameterTypeDescription
titlestringTitle for the duplicate
languagestringLanguage for the duplicate

Webhooks

Get notified when events happen. Register a URL and select which events to subscribe to. Every payload is HMAC-SHA256 signed so you can verify authenticity.

GET/api/v1/webhooks

List all webhooks in your organization.

POST/api/v1/webhooks

Create a new webhook. Returns the signing secret once. Payloads include X-Quaterio-Signature (HMAC-SHA256) and X-Quaterio-Event headers.

ParameterTypeDescription
urlrequiredstringHTTPS URL to receive webhook events
eventsrequiredstring[]Events to subscribe to: document.created, document.updated, document.deleted, document.exported, content.created, content.updated, content.deleted
descriptionstringOptional label for this webhook (max 500 chars)
curl -X POST https://quaterio.com/api/v1/webhooks \ -H "Authorization: Bearer q_..." \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/webhook", "events": ["document.exported", "document.updated"], "description": "Production notifications" }' # → { "ok": true, "data": { "id": "...", "secret": "shown-only-once" } }
GET/api/v1/webhooks/:id

Get a single webhook by ID.

DELETE/api/v1/webhooks/:id

Delete a webhook.

Verifying webhook signatures

Each delivery includes two headers for verification:

HeaderDescription
X-Quaterio-SignatureHMAC-SHA256 of the raw body, prefixed with sha256=
X-Quaterio-EventThe event type, e.g. document.exported
const crypto = require('crypto'); const signature = req.headers['x-quaterio-signature']; const expected = 'sha256=' + crypto .createHmac('sha256', webhookSecret) .update(rawBody) .digest('hex'); const valid = crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected), );

Errors

All errors return a consistent JSON structure with an error code and human-readable message.

{ "ok": false, "error": { "code": "VALIDATION_ERROR", "message": "templateId is required" } }
HTTP StatusDescription
400Validation error (missing or invalid parameters)
401Missing or invalid API token
403Insufficient tier (API requires Business or Enterprise)
404Resource not found
500Internal server error

Ready to integrate?

Sign up, design a document in the visual editor, generate your first PDF via API in about five minutes.