ideas.
October 09, 2026 4 min read apib2bcontent

Generate PDFs you can reproduce years later

A rendering API that pins fonts, engine version, and metadata so the same input produces identical PDF bytes years from now.

The idea

A hosted service that turns a JSON payload plus a versioned template into a PDF, and guarantees that feeding the same payload and template back in a year returns a byte-identical file. Every render emits a manifest recording the engine digest, the font bundle hash, the template commit, and a hash of the input. There is a /verify endpoint: hand it a PDF and its manifest, and it re-renders from scratch and tells you whether the bytes still match.

Why build this

Everyone who generates invoices, contracts, certificates, policy documents, or shipping paperwork eventually discovers their renderer is not a pure function. The usual setup is headless Chromium behind a template, and it drifts: a base image rebuild pulls a new Chrome, a font package changes, a glyph falls back to something else, line breaks move, and a two-page invoice becomes three. Nobody notices until an auditor asks for document 4,812 from eighteen months ago and the regenerated copy does not match the one the customer received.

The reproducible-builds world solved this for binaries years ago — pinned inputs, SOURCE_DATE_EPOCH, normalized metadata — and almost nobody has applied it to document rendering. Meanwhile the deadlines are arriving: EU e-invoicing mandates are phasing in through the late 2020s, and the obligation is not just to send a document but to archive something you can still produce on demand. Teams are being pushed from "we generate PDFs" to "we can prove what we generated", which is a different engineering problem.

Stack sketch

  • Engine: Typst as the primary renderer, pinned by image digest. It is fast, scriptable, and far more predictable than a browser. Keep a pinned Chromium worker as a second engine for tenants with existing HTML templates, but treat it as the legacy path.
  • Fonts: vendored into the worker image as a content-addressed bundle. The important design choice: if a glyph is missing, fail the render. No substitution, ever — silent fallback is the single biggest source of drift.
  • Determinism pass: set SOURCE_DATE_EPOCH, then normalize the output with pikepdf — fixed /CreationDate and /ModDate, deterministic /ID (qpdf --deterministic-id), stable XMP, no random object ordering.
  • Templates: a git repo per tenant, pulled by commit SHA. A render always names a commit; main is not a valid template reference.
  • API: Go with a Postgres-backed job queue. Synchronous for small documents, async with a callback above a few seconds.
  • Workers: one render per container, no network access, seccomp-confined, hard CPU and memory caps. Templates are untrusted code.
  • Storage: S3-compatible object store for artifacts, Postgres for manifests, with each manifest row carrying the hash of its predecessor so the log is tamper-evident.

Scope for v1

  • One engine (Typst), one template repo per tenant, JSON input.
  • Three endpoints: POST /render, GET /manifests/{id}, POST /verify.
  • Manifests with engine digest, font bundle hash, template commit, input hash, output hash.
  • A CLI that renders locally against the exact same pinned image, so developers can iterate without the API.
  • Deliberately out: PDF/A conformance claims, digital signatures, interactive form fields, arbitrary user-supplied HTML and CSS, and long-term archival storage. Store the artifacts yourself in v1; the manifest is the product.

Where it could go

The natural second step is standards conformance. PDF/A-3 plus an embedded Factur-X or ZUGFeRD XML payload turns the output into a hybrid invoice that European tax systems accept directly, and that is a feature customers will pay a real subscription for rather than a per-render fee. Qualified e-signature integration follows from the same place.

After that, the verification side becomes its own product. Publish manifest hashes to an append-only transparency log and a customer can prove to a third party — an auditor, a court, a counterparty — that a specific document existed in a specific form on a specific date, without trusting you. The third path is boring and lucrative: an on-premise build of the whole worker for regulated customers who cannot send contract data to a hosted API, licensed per node.

Watch out for

Font licensing is the first wall. Embedding and redistributing a font inside a hosted rendering service is a different licence grant from using it on a website, and a handful of popular commercial foundries forbid it outright — build the font bundle from clearly permissive families and make tenant-supplied fonts an explicit upload with an attestation. The second trap is subtler: engine and HarfBuzz upgrades can change glyph subsetting output even when the rendered page looks identical. Treat an engine bump as a new document version with its own manifest lineage, never a patch release that silently invalidates every hash you have issued.