Templates & fields

Discover template ids and the keys they accept.

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

GET/v1/templatesAuth required

Every template in the API key's workspace, whatever its status. Accepts limit (default 100, maximum 200), offset, status and kind — and nothing else.

bash
curl "https://api.pixldocs.com/v1/templates?limit=20&offset=0" \
  -H "Authorization: Bearer pxd_YOUR_KEY"
json
{
  "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
}
  • total is the count before limit — the whole matching set, not the page. Keep requesting with the next_offset you were given until has_more is false. Ordering is stable across pages, so a full sweep neither repeats nor drops a template.
  • kind is canvas or html — the substrate the template was authored on. It does not change how you call the render endpoints. Documents are authored on html.
  • 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_review or published) or ?kind= (canvas or html). 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_param naming the values that would have worked, and an unrecognised parameter is a 400 unknown_query_params. Neither is ignored: a mistyped filter must never come back as a 200 carrying the wrong set of templates.
  • default_format, width and height are null on templates that have never declared them. Treat them as hints, not guarantees.
  • preview_images is the rendered pages of the template, in order, as absolute URLs you can put straight in an <img>. It is always an array — empty, never null, when a template has no previews yet. thumbnail_url is 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

GET/v1/templates/:id/fieldsAuth required

The 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.

bash
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.

json
{
  "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"]
  }
}
json
{
  "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."
}
An empty 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

GET/v1/templates/:idAuth required

What 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.

bash
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.

json
{
  "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:

json
{
  "error": "unknown_fields",
  "unknown_fields": ["fullName", "role"],
  "accepted_keys": ["recipient_name", "course_title", "date"]
}
  • Applies to POST /v1/render and POST /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

DELETE/v1/templates/:idAuth required

Permanent. 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.

This example deletes something
The command below is the one destructive call in this reference, and it is written to fail if you paste it unchanged: 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.
bash
curl -X DELETE https://api.pixldocs.com/v1/templates/YOUR_TEMPLATE_ID \
  -H "Authorization: Bearer pxd_YOUR_KEY"
json
{
  "deleted": true,
  "template_id": "00000000-0000-4000-8000-000000000000",
  "name": "Course Completion Certificate",
  "assets_deleted": 3,
  "detached": { "renders": 128, "forms": 1 }
}
  • detached counts 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_found from 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 answer 403 template_forbidden, because there the distinction helps you debug and costs nothing.
  • Deleting an already-deleted id returns 404. Safe to retry.
There is no undo and no trash. If you are scripting cleanup, list first and delete by id — never loop over GET /v1/templates and delete everything it returns.