Recipe
Receipt template
A narrow thermal-style receipt template for the PaperPony PDF API, laid out as a single monospaced column on an A6 page with no logo fetch.
Worth knowing
- The page size comes from options.format, and A6 is the narrowest named format the API has. That is the one to use here: a receipt printer feeds a roll, and forcing A4 wastes most of the paper. There is no way to ask for a growing height.
- Monospaced figures matter more here than anywhere: a receipt is read as a column of numbers, and proportional digits break the alignment.
- There is no logo image. A receipt that waits on a network fetch is a receipt that prints late.
The template
Store it once with POST /v1/templates. It is compiled when you store it, so a mistake surfaces now rather than on the first render.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
font-family: 'Noto Sans', sans-serif;
font-size: 10pt;
line-height: 1.5;
color: #111827;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
table { border-collapse: collapse; width: 100%; }
thead { display: table-header-group; }
tr { break-inside: avoid; }
.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
body { font-family: 'Noto Sans Mono', monospace; font-size: 8.5pt; padding: 6mm 5mm; }
h1 { margin: 0 0 1mm; font-size: 12pt; text-align: center; letter-spacing: 0.1em; }
.centre { text-align: center; color: #4b5563; }
.rule { border-top: 1px dashed #9ca3af; margin: 4mm 0; }
.line { display: flex; justify-content: space-between; gap: 3mm; }
.line .label { overflow-wrap: anywhere; }
.total { font-weight: 700; font-size: 10pt; }
.thanks { margin-top: 6mm; text-align: center; color: #4b5563; }
</style>
</head>
<body>
<h1>{{merchant.name}}</h1>
<div class="centre">{{merchant.address}}</div>
{{#if merchant.tax_id}}<div class="centre">VAT {{merchant.tax_id}}</div>{{/if}}
<div class="rule"></div>
<div class="line"><span>Receipt</span><span>{{number}}</span></div>
<div class="line"><span>Date</span><span>{{formatDate issued_on locale=locale}}</span></div>
{{#if payment_method}}
<div class="line"><span>Paid by</span><span>{{payment_method}}</span></div>
{{/if}}
<div class="rule"></div>
{{#each items}}
<div class="line">
<span class="label">{{this.description}}</span>
<span>{{formatCurrency this.amount currency=../currency locale=../locale}}</span>
</div>
{{#if this.note}}<div class="centre" style="text-align:left"> {{this.note}}</div>{{/if}}
{{/each}}
<div class="rule"></div>
<div class="line"><span>Subtotal</span><span>{{formatCurrency subtotal currency=currency locale=locale}}</span></div>
{{#each tax_rows}}
<div class="line"><span>{{this.label}}</span><span>{{formatCurrency this.amount currency=../currency locale=../locale}}</span></div>
{{/each}}
<div class="line total"><span>Total</span><span>{{formatCurrency total currency=currency locale=locale}}</span></div>
<div class="thanks">{{footer_note}}</div>
</body>
</html>Rendering it
Substitute the template id you got back, and send this payload.
curl https://api.paperpony.dev/v1/pdf/render \
-H "Authorization: Bearer $PAPERPONY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_id": "tpl_YOUR_TEMPLATE_ID",
"data": {
"number": "R-104829",
"issued_on": "2026-07-26",
"currency": "EUR",
"locale": "de-DE",
"merchant": {
"name": "CAFE ATLAS",
"address": "Torstrasse 118, 10119 Berlin",
"tax_id": "DE812345678"
},
"payment_method": "Card ****4242",
"items": [
{
"description": "Flat white",
"amount": 4.2
},
{
"description": "Sourdough toast",
"amount": 6.5,
"note": "no butter"
},
{
"description": "Orange juice",
"amount": 3.8
}
],
"subtotal": 12.18,
"tax_rows": [
{
"label": "MwSt 19%",
"amount": 2.32
}
],
"total": 14.5,
"footer_note": "Vielen Dank!"
},
"options": {
"format": "A6",
"margin": {
"top": "0",
"right": "0",
"bottom": "0",
"left": "0"
},
"print_background": true
}
}'import { PaperPony } from 'paperpony';
const pony = new PaperPony({ apiKey: process.env.PAPERPONY_API_KEY });
const job = await pony.pdf.render({
"template_id": "tpl_YOUR_TEMPLATE_ID",
"data": {
"number": "R-104829",
"issued_on": "2026-07-26",
"currency": "EUR",
"locale": "de-DE",
"merchant": {
"name": "CAFE ATLAS",
"address": "Torstrasse 118, 10119 Berlin",
"tax_id": "DE812345678"
},
"payment_method": "Card ****4242",
"items": [
{
"description": "Flat white",
"amount": 4.2
},
{
"description": "Sourdough toast",
"amount": 6.5,
"note": "no butter"
},
{
"description": "Orange juice",
"amount": 3.8
}
],
"subtotal": 12.18,
"tax_rows": [
{
"label": "MwSt 19%",
"amount": 2.32
}
],
"total": 14.5,
"footer_note": "Vielen Dank!"
},
"options": {
"format": "A6",
"margin": {
"top": "0",
"right": "0",
"bottom": "0",
"left": "0"
},
"print_background": true
}
});
console.log(job.output_url);import os
import requests
response = requests.post(
"https://api.paperpony.dev/v1/pdf/render",
headers={"Authorization": f"Bearer {os.environ['PAPERPONY_API_KEY']}"},
json={
"template_id": "tpl_YOUR_TEMPLATE_ID",
"data": {
"number": "R-104829",
"issued_on": "2026-07-26",
"currency": "EUR",
"locale": "de-DE",
"merchant": {
"name": "CAFE ATLAS",
"address": "Torstrasse 118, 10119 Berlin",
"tax_id": "DE812345678"
},
"payment_method": "Card ****4242",
"items": [
{
"description": "Flat white",
"amount": 4.2
},
{
"description": "Sourdough toast",
"amount": 6.5,
"note": "no butter"
},
{
"description": "Orange juice",
"amount": 3.8
}
],
"subtotal": 12.18,
"tax_rows": [
{
"label": "MwSt 19%",
"amount": 2.32
}
],
"total": 14.5,
"footer_note": "Vielen Dank!"
},
"options": {
"format": "A6",
"margin": {
"top": "0",
"right": "0",
"bottom": "0",
"left": "0"
},
"print_background": true
}
},
)
response.raise_for_status()
print(response.json()["output_url"])