Delivery note
Render a delivery note in the background, get a notification when it is done, and bind it together with a PDF your customer supplied. You learn async rendering, webhooks, uploads and merging.
このレシピで身につくこと
エディタで作る
- Add the line item table and an acknowledgement signature area, exactly as in the invoice and contract recipes.
- The document itself is done in the editor. What this recipe teaches is what comes next — what you do with the file once it exists.
スキーマと API で作る
- When the document is large or the caller cannot wait, use POST /v1/create-async. It returns a job id immediately and renders from the queue.
- Send a webhook and we POST to it once when the job finishes. https only, and private or internal addresses are rejected before queueing — so you know before it costs you a render.
- The notification carries no download URL. Our URLs are themselves the authorisation, so putting one in a notification hands the document to whoever sees it. Fetch the URL with your own key via GET /v1/jobs/{id}.
- A notification is not evidence — it is unsigned and anyone can post the same shape to your endpoint. Treat it as "go and check", and let GET /v1/jobs/{id} decide what is true.
- Upload the customer PDF with POST /v1/files — the body is the file itself with content-type application/pdf. Up to 10MB and 100 pages, deleted after 24 hours. The page limit is separate on purpose: a 3,000-page PDF can be under 600KB, so size alone would let through a file that will not open on the phone of whoever receives it.
- Before joining anything, call GET /v1/files/{id} to see what it is: page count, per-page size and rotation, and whether it is encrypted. We cannot open an encrypted PDF.
- POST /v1/pdf/merge joins files in the order you list them, up to 20 at a time.
作ったあとに
Upload the customer PDF
POST /v1/files
(the body is the PDF itself · content-type: application/pdf)See what it is first
GET /v1/files/file_01J…
Join into one file
POST /v1/pdf/merge
{
"files": [
"file_01J…",
"file_01K…"
]
}出来上がったドキュメント
下の JSON は、2 つの道筋が生むドキュメントそのものです。テストがこれを検証し、警告なしでレンダリングされることも確認しているので、そのまま貼り付けてお使いいただけます。
{
"v": 1,
"unit": "px",
"page": {
"size": "a4",
"orientation": "portrait",
"margin": {
"t": 48,
"r": 48,
"b": 56,
"l": 48
},
"header": "{{ data.company }}",
"footer": "{{ data.no }} · [[page]] / [[pages]]"
},
"fonts": [],
"styles": [
{
"id": "s_h1",
"fontFamily": "Liberation Sans",
"fontSize": 22,
"fontWeight": 700,
"italic": false,
"color": "#111111",
"bg": "transparent",
"align": "left",
"valign": "top",
"lineHeight": 1.5,
"border": "none",
"radius": 0,
"opacity": 1
},
{
"id": "s_l",
"fontFamily": "Liberation Sans",
"fontSize": 12,
"fontWeight": 400,
"italic": false,
"color": "#333333",
"bg": "transparent",
"align": "left",
"valign": "top",
"lineHeight": 1.5,
"border": "none",
"radius": 0,
"opacity": 1
},
{
"id": "s_tbl",
"fontFamily": "Liberation Sans",
"fontSize": 11,
"fontWeight": 400,
"italic": false,
"color": "#111111",
"bg": "transparent",
"align": "left",
"valign": "middle",
"lineHeight": 1.5,
"border": "none",
"radius": 0,
"opacity": 1
}
],
"nodes": [
{
"id": "n_title",
"parent": "",
"order": 0,
"type": "label",
"x": 0,
"y": 0,
"w": 400,
"h": 28,
"show": "",
"styleId": "s_h1",
"props": {
"text": "Delivery note",
"src": "",
"fit": "",
"thickness": 0,
"stroke": "",
"fill": "",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Title"
},
{
"id": "n_meta",
"parent": "",
"order": 1,
"type": "label",
"x": 0,
"y": 34,
"w": 697,
"h": 18,
"show": "",
"styleId": "s_l",
"props": {
"text": "{{ data.customer }} · {{ data.no }}",
"src": "",
"fit": "",
"thickness": 0,
"stroke": "",
"fill": "",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Customer"
},
{
"id": "n_items",
"parent": "",
"order": 2,
"type": "table",
"x": 0,
"y": 76,
"w": 697,
"h": 180,
"show": "",
"styleId": "s_tbl",
"props": {
"text": "",
"src": "",
"fit": "",
"thickness": 1,
"stroke": "#c9ced6",
"fill": "#f3f5f7",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 5,
"padX": 8,
"align": "",
"valign": "middle",
"wrap": "",
"bind": "data.items",
"headerHeight": 26,
"rowHeight": 22,
"max": 100,
"columns": [
{
"header": "Item",
"cell": "{{ item.name }}",
"width": 397,
"align": "left"
},
{
"header": "Qty",
"cell": "{{ item.qty }}",
"width": 120,
"align": "right"
},
{
"header": "Amount",
"cell": "{{ format(item.amount, \"currency\", \"USD\", \"en-US\") }}",
"width": 180,
"align": "right"
}
],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Line items"
},
{
"id": "n_rule",
"parent": "",
"order": 3,
"type": "line",
"x": 397,
"y": 300,
"w": 300,
"h": 2,
"show": "",
"styleId": "s_l",
"props": {
"text": "",
"src": "",
"fit": "",
"thickness": 1,
"stroke": "#111111",
"fill": "",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Signature rule"
},
{
"id": "n_ack",
"parent": "",
"order": 4,
"type": "label",
"x": 397,
"y": 306,
"w": 300,
"h": 18,
"show": "",
"styleId": "s_l",
"props": {
"text": "Received by (signature)",
"src": "",
"fit": "",
"thickness": 0,
"stroke": "",
"fill": "",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Acknowledgement"
}
]
}リクエストの本文です。上のドキュメントを template に、下のデータを data に入れてください。
{
"template": "(the document above)",
"data": {
"company": "BrewMyPDF",
"customer": "Hanbit Corp.",
"no": "DN-2026-0311",
"items": [
{
"name": "Mug",
"qty": 12,
"amount": 180
},
{
"name": "Sticker set",
"qty": 40,
"amount": 120
}
]
},
"webhook": "https://example.com/hooks/BrewMyPDF"
}