Skip to main content

Invoice Template API

Read and publish the template your invoices are rendered with. The invoice template editor in the web interface is built on these three endpoints, and they are what you use to publish a template from your own code.

Every endpoint on this page accepts either an admin session token (Authorization: Bearer <JWT>) or an admin-role API key in X-API-Key. See Authentication. Each one acts on one environment, picked by the X-Environment header with a session token, or by the key's own environment with an API key. Errors on this page come back as {"detail": ...}, not in the error object other endpoints use.

A template is plain text. Everything in it is fixed except a few marked regions, such as the style, the header and the footer. The line-item tables and totals sit outside those regions, so no edit can break them.

Get the Template's Status​

GET /api/v1/templates/invoice

Response:

{
"uploaded": true,
"managed": true,
"template_id": "12",
"name": "PortaAIM Default",
"state": { "v": 1, "style": { "...": "..." }, "logo": { "...": "..." }, "header_html": "..." },
"checksum": "9f2c1a0b..."
}
FieldDescription
uploadedtrue when this environment has an invoice template.
managedtrue when the template was published through this API, so its regions can be read back and changed.
template_idId of the template. You do not need it to publish.
nameName of the template.
stateThe editor state the template was built from, recovered from the template itself. null when it is not managed.
checksumChecksum of the template in place. Send it back as expected_checksum when you publish.

The status can be up to a minute old. After you publish, take the checksum for your next publish from the publish response rather than reading the status again.

Get the Base Template​

GET /api/v1/templates/invoice/base

Returns the base template as text/plain, the same for every tenant. Each editable region is enclosed in a pair of comments, such as <!-- PAIM:HEADER:BEGIN --> and <!-- PAIM:HEADER:END -->, and the markers are the list of regions. Build the template you publish from this text by replacing only what sits between those pairs. The base can change between releases, so fetch it again before each publish rather than storing an assembled template.

The state region carries the state you publish. Between <!-- PAIM:STATE:BEGIN --> and <!-- PAIM:STATE:END -->, put one comment, <!-- PAIM:STATE <state> -->, where <state> is your state object serialized as JSON and base64-encoded. Send the same object as state in the request. If you edit only the HTML regions, an empty object {} is a valid state: every field takes its default.

Publish the Template​

PUT /api/v1/templates/invoice

Request body:

FieldTypeRequiredDescription
tmplstringYesThe complete template, at most 512 KiB.
stateobjectYesThe editor state the template was built from. It must equal the state embedded in tmpl. The full shape is in the API reference at /docs.
expected_checksumstringNoThe checksum you last read. Omit it to publish over whatever is in place.
mirror_to_other_envbooleanNoAlso publish the same template to your other environment. Default false.

Response:

{ "template_id": "12", "checksum": "4b7e90d1...", "mirrored": false }

Besides a 200, the request can answer:

  • 422 when validation refuses the template, and nothing is published. detail says why. Everything outside the marked regions must match the base template byte for byte, the style region holds CSS only, the other regions refuse scripts and event handlers, template directives are limited to a fixed set of fields, and the logo must be a PNG or JPEG. A request body that does not match the schema also answers 422, with detail as a list of field errors.
  • 409 when the template in place changed since you read expected_checksum. detail.current_checksum carries the current checksum, so read the status again and reapply your edit.
  • 400 when you ask to mirror without a session for the other environment. An API key acts in one environment only, so mirroring needs a session token.
  • 502 when the template was published to this environment but mirroring to the other one failed. Do not repeat the publish here; publish again from the other environment instead.

Mirroring carries no checksum check, so it publishes over whatever the other environment holds. The billing engine has no atomic swap either, so two publishes that land at the same moment end with the later one in place.