# pagenull: API reference > pagenull is an HTML-to-PDF and screenshot API. Send HTML or a public URL and get the PDF or image back in the same response, with an Ed25519-signed receipt over the exact output. Request content and output are not stored. It is hosted in Helsinki, Finland, with EU sub-processors only. The free plan includes 100 renders a month. Generated from the same sources as https://pagenull.dev/docs/api and https://pagenull.dev/openapi.json. ## Authentication Base URL https://api.pagenull.dev. Send your key as `Authorization: Bearer ` or `X-API-Key: `. Keys are on https://pagenull.dev/dashboard/keys. ## Endpoints | Endpoint | What it does | |---|---| | `POST /v1/pdf` | Render HTML or a URL to PDF | | `POST /v1/screenshot` | Render HTML or a URL to PNG or JPEG | | `GET /v1/templates` | List your saved templates and the built-in ones | | `POST /v1/templates` | Save a template | | `DELETE /v1/templates/{id}` | Delete a saved template | | `POST /v1/templates/{id}/render` | Render a template with JSON data | | `POST /v1/invoices` | Create a Factur-X / ZUGFeRD e-invoice (PDF/A-3b with the EN 16931 XML embedded) | | `GET /v1/usage` | This month's usage and limits | | `POST /api/receipts/verify` | Check a receipt's signature, and optionally a file's SHA-256 | | `GET /.well-known/pagenull-keys.json` | The public keys receipts are signed with (JWK set) | | `GET /health` | Service health | ## Options for POST /v1/pdf and POST /v1/screenshot | Field | Type | Default | Description | |---|---|---|---| | `html` | string | — | HTML to render. Up to 5 MB. Provide exactly one of html or url. | | `url` | string | — | Public http(s) URL to load. Private, loopback, link-local and metadata addresses are refused. | | `javascript` | boolean | true | Run page scripts. Turn off for untrusted HTML. | | `allowNetwork` | boolean | true | Let the page load images, fonts and CSS from public hosts. false blocks every request except the page itself. | | `waitUntil` | "load" \| "domcontentloaded" \| "networkidle" | "load" | When the page counts as loaded. | | `waitFor` | string | — | CSS selector to wait for before capturing, e.g. "#chart[data-ready]". | | `timeoutMs` | integer | 30000 | Give up after this long (1000–60000). | | `emulateMedia` | "print" \| "screen" | pdf: "print", screenshot: "screen" | Which CSS media type applies. | | `printReset` | boolean | true | Print-friendly defaults the page can override: exact colours, table headers that repeat on every page, rows and images that don't split, headings kept with what follows. false leaves the page exactly as written. | ### PDF only | Field | Type | Default | Description | |---|---|---|---| | `format` | "A4" \| "Letter" \| "Legal" \| "A3" \| "A5" \| "Tabloid" | "A4" | Paper size. | | `width / height` | length | — | Custom page size, e.g. "80mm" × "200mm" for thermal receipts. Set both; overrides format. | | `landscape` | boolean | false | Rotate the page. | | `margin` | { top, right, bottom, left } | 12mm | Each a length with a unit: "12mm", "0.5in", "1cm", "24px". Without it, 12 mm on every side, set as a CSS @page rule, so margins in your own @page CSS win. | | `printBackground` | boolean | true | Print background colours and images. | | `headerHtml / footerHtml` | string | — | HTML repeated on every page. Use and . Set a font-size (e.g. 9px); styles from the page don't apply. Needs a matching margin. | | `scale` | number | 1 | 0.1–2. | | `pageRanges` | string | all | e.g. "1-3, 5". | | `preferCssPageSize` | boolean | true | A CSS @page size rule wins over format. Set false to make format win. | | `pageNumbers` | boolean \| string | false | Page numbers in a page margin. true prints "Page {page} of {pages}"; a string is your own text with {page} and {pages}, up to 80 characters. | | `pageNumbersPosition` | "bottom-right" \| "bottom-center" \| "bottom-left" \| "top-right" \| "top-center" \| "top-left" | "bottom-right" | Where the page numbers go. | ### Screenshot only | Field | Type | Default | Description | |---|---|---|---| | `type` | "png" \| "jpeg" | "png" | Image format. | | `quality` | integer | 85 | JPEG quality, 1–100. | | `width / height` | integer | 1280 / 800 | Viewport size in CSS pixels (100–4000). | | `fullPage` | boolean | false | Capture the whole scrollable page. | | `deviceScaleFactor` | number | 1 | 1–3. Use 2 for retina-sharp images. | ### Delivery and test renders (all render endpoints) | Field | Type | Default | Description | |---|---|---|---| | `deliverTo` | string (https URL) | — | A presigned PUT URL in your own storage (S3, R2, GCS, Azure, MinIO). We upload the output there, answer 201 with its size and SHA-256, and keep nothing. Redirects aren't followed; private addresses are refused. | | `test` | boolean | false | Test render: watermarked, free, doesn't count against your quota. | | `filename` | string | "document" | Name used in the Content-Disposition header. | ## POST /v1/invoices (Factur-X / ZUGFeRD) | Field | Type | Default | Description | |---|---|---|---| | `invoice` | object | — | The invoice. Fields below. | | `profile` | "EN16931" \| "BASIC" \| "MINIMUM" | "EN16931" | Factur-X profile. EN16931 carries the full standard and is what most buyers expect. BASIC drops some optional fields. MINIMUM carries only the header and totals and doesn't count as an e-invoice in Germany. | | `language` | string | "en-GB" | Locale for amounts and dates on the printed side, e.g. "de-DE", "fr-FR". | | `template` | string | "invoice" | Printed layout: the built-in invoice template or one of your saved template ids. It receives the computed values, already formatted. | | `options` | object | {} | Page options as for /v1/pdf (format, margin, pageNumbers …). | | `deliverTo / test / filename` | | | As for /v1/pdf. filename defaults to the invoice number. | ### The invoice object | Field | Type | Default | Description | |---|---|---|---| | `type` | "invoice" \| "credit_note" | "invoice" | A credit note lists the credited amounts as positive numbers, and can name precedingInvoice. | | `number` | string | — | Your invoice number. | | `issueDate / dueDate / supplyDate` | YYYY-MM-DD | supplyDate = issueDate | Dates. supplyDate is the date of supply. | | `currency` | ISO 4217 | "EUR" | Three-letter currency code, e.g. "EUR", "BGN", "USD". | | `buyerReference / orderReference` | string | — | The buyer's reference (e.g. their PO number) and order number. | | `seller / buyer` | { name, address, vatId, legalId, email } | — | address is { line1, line2, city, postcode, country } with a 2-letter country code. vatId has its country prefix, e.g. "DE123456789". The seller's is required unless every line is O. | | `lines[]` | { description, detail, quantity, unit, unitPrice, vatCategory, vatRate } | unit: "C62" | Net unit price before VAT, up to 4 decimals. unit is a UN/ECE Rec 20 code: C62 (one), HUR (hour), DAY, MON, KGM … | | `payment` | { iban, bic, accountName, reference, terms } | — | SEPA credit transfer details. reference defaults to the invoice number. | | `paidAmount` | number | — | Already paid; the amount due is the total minus this. | | `vatExemptionReason` | string | per category | Printed and embedded for E, AE, K, G and O lines. The default is the category name, e.g. "Reverse charge". | | `note` | string | — | Free text on the invoice. | | `precedingInvoice` | { number, issueDate } | — | For a credit note: the invoice it corrects. | ## Response headers | Header | Meaning | |---|---| | `pagenull-retention` | Always `none`: nothing from the request or its output was stored. | | `pagenull-region` | Where the render ran, e.g. `hetzner-helsinki`. Also signed into the receipt. | | `pagenull-render-id` | The id of this render. It's what we log; quote it to support. | | `pagenull-renderer` | The renderer build: a SHA-256 of the renderer's code, dependency versions and Chromium version. | | `pagenull-receipt` | Ed25519-signed receipt over the render id, the SHA-256 of the output, the time, the region, the renderer build and `stored: false`. Check it at https://pagenull.dev/verify or with the key at /.well-known/pagenull-keys.json. | | `pagenull-pages` | Pages in the output (1 for screenshots). | | `pagenull-warnings` | Only present when something looked wrong; comma-separated. The render still succeeded. The values: | | `pagenull-einvoice` | /v1/invoices only: the Factur-X profile, e.g. `factur-x; profile=EN16931; pdfa=3b`. | | `x-render-ms` | Time spent in Chromium for this render, in milliseconds. | | `x-quota-limit` | Renders included per month on your plan. | | `x-quota-used` | Renders counted this month, after this request. | | `x-ratelimit-limit` | Requests allowed per minute for this key. | | `x-ratelimit-remaining` | Requests left in the current minute for this key. | | `x-ratelimit-reset` | Seconds until the minute resets. | | `idempotent-replayed` | `true` when this was a retry of an earlier request with the same Idempotency-Key, rendered again and not charged again. | | `content-disposition` | `inline; filename="…"`, from `filename` (default `document`). | | `retry-after` | Seconds to wait before retrying. | ## Warnings (pagenull-warnings) | Value | Meaning | |---|---| | `missing_glyphs: