← レシピ一覧

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.

このテンプレートを取り込む

このレシピで身につくこと

  • api-async
  • api-webhook
  • pdfop-upload
  • pdfop-info
  • pdfop-merge
  • node-line

エディタで作る

  1. Add the line item table and an acknowledgement signature area, exactly as in the invoice and contract recipes.
  2. 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 で作る

  1. 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.
  2. 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.
  3. 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}.
  4. 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.
  5. 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.
  6. 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.
  7. 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"
}