API reference / Generate a document
Generate a document
One endpoint turns a published template plus JSON into a PDF.
POST /v1/generateRequest
Send a JSON body with two fields, and your API key in the
Authorization header.
| Field | Type | Description |
|---|---|---|
| templateId | stringrequired | The code of a published template you own (looks like tpl_invoice_8x2k). The API renders its currently published version. |
| data | objectrequired | A JSON object whose keys fill the template's variables. It must be an object, not an array or null. |
curl -X POST https://api.pdfglyph.dev/v1/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateId": "tpl_invoice_8x2k",
"data": {
"customer": "Northwind Ltd",
"number": "INV-2048",
"items": [
{
"description": "Consulting",
"amount": "€900.00"
},
{
"description": "Support",
"amount": "€340.00"
}
],
"total": "€1,240.00"
}
}' \
--output document.pdfResponse
On success the API returns:
201withContent-Type: application/pdf. The response body is the raw PDF bytes (sent inline asdocument.pdf), ready to save, email, or stream on to your user.X-Generation-Id— the id of the logged generation, useful for correlating with your history.X-Render-Duration-Ms— how long the render took, in milliseconds.
There is no JSON envelope around a success: the bytes are the document. Write them straight to a file or a response stream.
Idempotency
Pass an optional Idempotency-Key header — any opaque string unique to the call (a UUID is
ideal) — to make a generation safe to retry. If a request is lost on the wire and you retry with
the same key, you get the first outcome back instead of a second charge.
Idempotency-Key: 1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed- Replay. Within a 24-hour window, a repeat with the same key and the same body
returns the original generation — the same
X-Generation-Id, billed exactly once — and addsX-Idempotent-Replayed: true. Because PDFs are never stored, the bytes are re-rendered from the exact (immutable) template version the first call used, so the document is identical even if you have published a new version since. Send the same body on the retry, since it is re-rendered. - Conflict. Reusing a key with a different body returns
idempotency_conflict(409) — a key must map to one request. Use a fresh key for a new request. - Still in flight. If the first request is still rendering when the retry arrives, you get a
transient
service_unavailable(503); retry shortly with backoff. - Scope & expiry. Keys are scoped to your account and expire after 24 hours, after which the same string is free to start a new generation.
A failed call stores no key — it consumed no quota, so retrying it (with or without the same
key) simply re-attempts. The Node SDK sends this header for you when you pass
idempotencyKey to generate(...).
Errors
Failures return JSON instead of a PDF, with this shape and the matching HTTP status:
{
"error": {
"code": "invalid_data",
"message": "Variable 'items' must be a list, but got a string."
}
}The set of codes is closed and documented in Errors. A few worth knowing here:
- The body must be valid JSON, or you get
invalid_json(400). datamust satisfy the template's variables, or you getinvalid_data(422) listing what is wrong.- An unknown or unpublished template returns
template_not_found(404) ortemplate_not_published(422).
A failed call never consumes quota, so you can retry safely once you have fixed the cause.
Notes
- The endpoint is server-to-server. Call it from your backend with a key kept secret.
- The render has a 30-second budget and external assets a 10-second timeout each. See Fonts & images.
- Throughput and monthly volume are bounded per plan. See Rate limits & quotas.