Appearance
Template versions
A version is a snapshot of a template's content that never changes again, with an optional message recording what changed. Render one by adding an @ suffix to the template_id — report@3, or report@latest for the newest. See Versions & Tags for how versions and tags fit together.
Publish a version of a template
POST
/v2/templates/{templateId}/versions
Stores a read-only snapshot of the template's current content and returns the version number it was given, with 201. Numbers start at 1 and count up. The optional message records what changed. Publishing content identical to the newest version stores nothing and answers 200 with that existing version, under the message it was already published with — a message sent alongside unchanged content is discarded.
Authorizations
BearerAuth
Your Papermill API key, sent as Authorization: Bearer <key>.
Type
HTTP (bearer)
or
ApiKeyHeader
Your Papermill API key. Equivalent to the bearer form.
Type
API Key (header: x-api-key)
Parameters
Path Parameters
templateId*
Type
Requiredstring
Min Length
1Max Length
128Pattern
"^(?!__.*__$)[A-Za-z0-9_-]+$"Request Body
application/json
JSON "message": "string"
{
}
Responses
Content unchanged; the version that already held it
application/json
JSON "version": 0, "sha256": "string", "message": "string", "created_at": "string", "created_by": { "id": "string", "name": "string", "email": "string" }
{
}
List a template's versions
GET
/v2/templates/{templateId}/versions
A template's published versions, newest first and without their content. has_more is true when the template holds versions older than the last one returned; those are read one at a time by number, since numbers count up from 1 with no gaps.
Authorizations
BearerAuth
Your Papermill API key, sent as Authorization: Bearer <key>.
Type
HTTP (bearer)
or
ApiKeyHeader
Your Papermill API key. Equivalent to the bearer form.
Type
API Key (header: x-api-key)
Parameters
Path Parameters
templateId*
Type
Requiredstring
Min Length
1Max Length
128Pattern
"^(?!__.*__$)[A-Za-z0-9_-]+$"Responses
Published versions
application/json
JSON "versions": [ { "version": 0, "sha256": "string", "message": "string", "created_at": "string", "created_by": { "id": "string", "name": "string", "email": "string" } } ], "has_more": true
{
}
Read one version of a template
GET
/v2/templates/{templateId}/versions/{version}
Returns a published version and its content. The content carries a header comment naming the version, the time it was published, who published it, and the sha256 of the content below the header — remove the header and hash what remains to check it.
Authorizations
BearerAuth
Your Papermill API key, sent as Authorization: Bearer <key>.
Type
HTTP (bearer)
or
ApiKeyHeader
Your Papermill API key. Equivalent to the bearer form.
Type
API Key (header: x-api-key)
Parameters
Path Parameters
templateId*
Type
Requiredstring
Min Length
1Max Length
128Pattern
"^(?!__.*__$)[A-Za-z0-9_-]+$"version*
Type
Requiredstring
Min Length
1Max Length
16Responses
The published version
application/json
JSON "version": 0, "sha256": "string", "message": "string", "created_at": "string", "created_by": { "id": "string", "name": "string", "email": "string" }, "content": "string"
{
}
Example
Publish the template's current content with a note on what changed, then render the version you just published. Publishing is safe to repeat: identical content stores nothing and answers 200 with the existing version rather than a new number.
shell
set -e
version=$(curl -fsS -X POST "https://api.papermill.io/v2/templates/$TEMPLATE_ID/versions" \
-H "Authorization: Bearer $PAPERMILL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "Switch the cover to the new brand marks"}' | jq -er .version)
curl -X POST "https://api.papermill.io/v2/pdf?template_id=$TEMPLATE_ID@$version" \
-H "Authorization: Bearer $PAPERMILL_API_KEY" \
-H "Content-Type: application/json" \
-o report.pdf \
-d '{"customer": "Acme Co"}'python
import os
import requests
key = os.environ["PAPERMILL_API_KEY"]
template_id = os.environ["TEMPLATE_ID"]
auth = {"Authorization": f"Bearer {key}"}
published = requests.post(
f"https://api.papermill.io/v2/templates/{template_id}/versions",
headers=auth,
json={"message": "Switch the cover to the new brand marks"},
)
published.raise_for_status()
version = published.json()["version"]
pdf = requests.post(
"https://api.papermill.io/v2/pdf",
params={"template_id": f"{template_id}@{version}"},
headers={**auth, "Content-Type": "application/json"},
json={"customer": "Acme Co"},
)
pdf.raise_for_status()
with open("report.pdf", "wb") as f:
f.write(pdf.content)javascript
import fs from 'node:fs/promises'
const key = process.env.PAPERMILL_API_KEY
const templateId = process.env.TEMPLATE_ID
const auth = { Authorization: `Bearer ${key}` }
const published = await fetch(`https://api.papermill.io/v2/templates/${templateId}/versions`, {
method: 'POST',
headers: { ...auth, 'Content-Type': 'application/json' },
body: JSON.stringify({ message: 'Switch the cover to the new brand marks' }),
})
if (!published.ok) throw new Error(`${published.status} ${published.statusText}`)
const { version } = await published.json()
const pdf = await fetch(`https://api.papermill.io/v2/pdf?template_id=${templateId}@${version}`, {
method: 'POST',
headers: { ...auth, 'Content-Type': 'application/json' },
body: JSON.stringify({ customer: 'Acme Co' }),
})
if (!pdf.ok) throw new Error(`${pdf.status} ${pdf.statusText}`)
await fs.writeFile('report.pdf', Buffer.from(await pdf.arrayBuffer()))Verifying content you were sent
GET …/versions/{version} returns the content with a header comment naming the version and the sha256 of everything below it:
xml
<!-- Papermill template version 3
sha256 d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592
2026-08-06T09:00:00Z by Ada Lovelace <ada@example.com> -->The header is a label rather than proof — anyone who edits the content can recompute the digest inside it. To check a file really is the version it claims to be, remove the header, hash what remains, and compare that against the sha256 this endpoint returns. The digest does not cover the header itself, which is what makes the two comparable.