Before you can render, you need two things: the template_id and the exact keys that template accepts in data. These two endpoints give you both, so you never have to guess a field name.
List templates
/v1/templatesAuth requiredEvery template in the API key's workspace, whatever its status. Accepts limit (default 100, maximum 200), offset, status and kind — and nothing else.
curl "https://api.pixldocs.com/v1/templates?limit=20&offset=0" \
-H "Authorization: Bearer pxd_YOUR_KEY"{
"templates": [
{
"id": "00000000-0000-4000-8000-000000000000",
"name": "Course Completion Certificate",
"kind": "canvas",
"category": "certificate",
"status": "published",
"default_format": "pdf",
"width": 842,
"height": 595,
"page_count": 1,
"thumbnail_url": "https://cdn.pixldocs.com/template-previews/ck-certificate.png",
"preview_images": [
"https://cdn.pixldocs.com/template-previews/ck-certificate.png"
],
"updated_at": "2026-07-31T08:35:48.277Z"
}
],
"count": 1,
"total": 214,
"limit": 20,
"offset": 0,
"status": null,
"kind": null,
"has_more": true,
"next_offset": 20
}totalis the count beforelimit— the whole matching set, not the page. Keep requesting with thenext_offsetyou were given untilhas_moreisfalse. Ordering is stable across pages, so a full sweep neither repeats nor drops a template.kindiscanvasorhtml— the substrate the template was authored on. It does not change how you call the render endpoints. Documents are authored onhtml.- Unfiltered, the list returns every status: drafts and templates awaiting review come back alongside published ones, because a key may legitimately render its own drafts. Narrow it with
?status=(draft,pending_revieworpublished) or?kind=(canvasorhtml). The response echoes both back so you can confirm the filter was applied without counting rows. - A value outside those sets is a
400 invalid_query_paramnaming the values that would have worked, and an unrecognised parameter is a400 unknown_query_params. Neither is ignored: a mistyped filter must never come back as a200carrying the wrong set of templates. default_format,widthandheightarenullon templates that have never declared them. Treat them as hints, not guarantees.preview_imagesis the rendered pages of the template, in order, as absolute URLs you can put straight in an<img>. It is always an array — empty, nevernull, when a template has no previews yet.thumbnail_urlis the card-sized one for a grid, and falls back to the first page. Build your gallery from these rather than copying our images into your app.
Inspect a template's fields
/v1/templates/:id/fieldsAuth requiredThe authoritative list of keys this template accepts. single_fields are top-level keys in data; repeatable_sections are arrays of row objects (line items, ticket rows, table bodies).
A section's section_id is a readable name taken from its label —personal_details, line_items. Send its rows as an array under that key, or as flat field_<section_id>_<n>_<column> keys, where <n> starts at 1. The internal editor ids listed under aliases stay accepted, so existing integrations keep working — you should never need to write one.
curl https://api.pixldocs.com/v1/templates/YOUR_TEMPLATE_ID/fields \
-H "Authorization: Bearer pxd_YOUR_KEY"canonical_keys is one name per field, in declaration order — the list to code against. key_aliases lists the older spellings we still accept for those keys, and accepted_keys is the full set the render guard checks against. If you are generating code from this response, read canonical_keys and ignore the rest.
{
"canonical_keys": ["company_name", "invoice_no", "line_items"],
"key_aliases": {
"company_name": ["field_company_name"],
"invoice_no": ["field_invoice_no"],
"line_items": ["sec_line_items", "line-items"]
}
}{
"template_id": "00000000-0000-4000-8000-000000000000",
"name": "Course Completion Certificate",
"default_format": "pdf",
"single_fields": [],
"repeatable_sections": [],
"note": "No fillable fields yet — bind variable roles in the editor."
}single_fields means the template has no bound fields yet: it will render, but it will render the same static document for every request. Put the variable parts in the template as {{ placeholders }} — the field contract above is parsed from them — before you wire it into your app.Read a template back
/v1/templates/:idAuth requiredWhat we actually stored, at any time — not just in the response to your write. The API resolves a few things at save time (a CSS font stack becomes a bundled family, for instance), so this is how you confirm the bytes on our side match the bytes you sent. source_sha256 is a SHA-256 over html_sourceexactly as stored, so you can diff without reading.
curl https://api.pixldocs.com/v1/templates/YOUR_TEMPLATE_ID \
-H "Authorization: Bearer pxd_YOUR_KEY"?version=draft (the default) is the working copy — the bytes PATCH writes. ?version=published is the pinned version POST /v1/render serves. resolved tells you which you got: published means a real pin, unpublished means this template has never been published so the live row answered.
{
"template_id": "00000000-0000-4000-8000-000000000000",
"name": "Invoice",
"kind": "html",
"version": "draft",
"resolved": "draft",
"snapshot_id": null,
"source_available": true,
"html_source": "{% comment %}\n@field invoice_no text\n{% endcomment %}...",
"html_styles": "h1 { font-family: Inter, sans-serif }",
"source_sha256": "9f2c…",
"canonical_keys": ["invoice_no"]
}The public demo key can read a published gallery template's shape and fields, but not its source: those responses carry source_available: false. Use your own key on your own template to read the bytes.
Keys that matched nothing
A key this template cannot bind is refused. Nothing renders, no credit is used, and the response names both the offending keys and the ones it does accept:
{
"error": "unknown_fields",
"unknown_fields": ["fullName", "role"],
"accepted_keys": ["recipient_name", "course_title", "date"]
}- Applies to
POST /v1/renderandPOST /v1/renders. - A batch is rejected whole — one bad item never half-renders 500 wrong documents.
- A renamed field breaks loudly at the first call instead of producing plausible documents carrying placeholder text.
Delete a template
/v1/templates/:idAuth requiredPermanent. The template row and every image, logo and asset it uploaded are removed from storage, so a template your agent created and no longer needs stops costing you anything.
YOUR_TEMPLATE_ID is not a UUID, so the API answers 400 invalid_template_id rather than removing a document. Substitute an id you meant to destroy.curl -X DELETE https://api.pixldocs.com/v1/templates/YOUR_TEMPLATE_ID \
-H "Authorization: Bearer pxd_YOUR_KEY"{
"deleted": true,
"template_id": "00000000-0000-4000-8000-000000000000",
"name": "Course Completion Certificate",
"assets_deleted": 3,
"detached": { "renders": 128, "forms": 1 }
}detachedcounts what survives the delete. Past renders stay in your history and their file URLs keep working — they just no longer point at a template.- A form built on the template keeps its submissions but stops rendering. Delete the form too, or repoint it, before you delete a template that is live somewhere.
- A template in another workspace returns
404 template_not_foundfrom this endpoint — the same answer as an id that never existed, so a key cannot use delete to probe for documents it does not own. The read and render endpoints are franker about it and answer403 template_forbidden, because there the distinction helps you debug and costs nothing. - Deleting an already-deleted id returns
404. Safe to retry.
GET /v1/templates and delete everything it returns.