POST /v1/render/html

Render your own HTML — no template needed.

Send your own HTML and CSS and get a document back. No template to create, publish or version — the request body is the template. Use this when the markup already lives in your codebase and you only want the rendering.

POST/v1/render/htmlAuth required
Everything else matches POST /v1/render: the same authentication, the same credit cost, the same error envelope, the same response headers, and your workspace brand kit is applied the same way.

Body parameters

htmlrequired
string
The document markup. Liquid is supported, so {{ customer_name }} is filled in from data. Maximum 2 MB including styles.
styles
string
CSS for the document. Kept separate from the markup so it can be reused across requests.
data
object
Values for the Liquid variables in your HTML. Any key you send is available — there is no field contract to declare first.
page_setup
object
Page geometry: size, orientation, margin, and running header/footer slots (which accept {{page}} and {{pages}}).
format
string
default: pdf
Output format. Raster formats return page 1 only for a multi-page document, and say so in X-Pixldocs-Pages-Dropped.
pdfpngwebp
scale
number
default: 1
Output scale multiplier for raster formats, 0.1–4. Ignored for PDF.
watermark
boolean
Force the watermark on. Test keys always watermark.

Example request

bash
curl -X POST https://api.pixldocs.com/v1/render/html \
  -H "Authorization: Bearer pxd_YOUR_KEY" \
  -H "Content-Type: application/json" \
  --output receipt.pdf \
  -d '{
    "html": "<h1>Receipt {{ number }}</h1><p>Thanks, {{ customer }}.</p>",
    "styles": "body { font-family: Helvetica; } h1 { letter-spacing: 0.02em; }",
    "page_setup": { "size": "A4", "margin": { "top": "20mm", "bottom": "20mm" } },
    "data": { "number": "R-1041", "customer": "Acme Corp" }
  }'

Images

Every <img> is fetched and inlined before the page is rendered — the renderer itself loads nothing from the network. Images must be hosted on an allowed host (your PixlDocs storage or your configured asset hosts); anything that cannot be inlined is a loud 422 rather than a blank rectangle inside a 200.

Barcodes and QR codes

Two filters turn a value into a scannable code, so a delivery note, packing slip, ticket or asset label does not have to be retyped at the other end. {{ order_id | barcode }} produces Code 128, and {{ url | qr: "H" }} a QR code at the given error-correction level (L, M, Q, H). Both return a data URI for an <img src>, so you place and size the code with ordinary CSS — in millimetres, because what a scanner sees is the printed width. Both emit SVG, so the code stays vector in the PDF and is sharp at any size.

An empty value is 400 liquid_filter_error, not a blank square: a code nobody can scan reads as a printer fault, and a code carrying the wrong payload is worse. Guard genuinely optional codes with {% if tracking_number != blank %}. Code 128 covers ASCII only and refuses anything longer than 48 characters, both because the alternative is a code that scans and returns the wrong string.

html
<img class="label-code" src="{{ tracking_number | barcode }}">
<img class="label-qr"   src="{{ tracking_url | qr: 'H' }}">

Fonts

The renderer never touches the network, so it prints in the 62 families it bundles (GET /v1/fonts). Your font-family stacks are read the way CSS defines them: the first bundled family in the list wins, and a stack of faces we do not bundle resolves to a bundled family of the same class — Helvetica, Arial, sans-serif prints in Inter, Georgia, serif in Lora, Menlo, monospace in JetBrains Mono.

This is never silent: any stack we resolve is listed in the X-Pixldocs-Font-Substituted response header. Name a bundled family first and the header is absent, which is the check to make in CI if the exact typeface matters to you.

Because nothing is fetched during a render, a @font-face pointing at a URL — or a Google Fonts @import or <link> — is refused with 400 remote_font_not_allowed rather than loaded. To print in a face we do not bundle, inline it: src: url(data:font/woff2;base64,…) renders exactly as written.

When to save a template instead

Save a template when the markup is stable and you want versioning, a published/draft split, a declared field contract, a shareable preview, or renders from Make, Zapier, n8n and MCP clients that only pass data. Raw HTML is for when your application already owns the markup and PixlDocs is purely the renderer.

Errors

Unknown body keys are refused with 400 unknown_fields rather than ignored, a Liquid mistake is 400 liquid_syntax_error with its line number, markup the sanitiser rejects is 400 unsafe_html, a webfont we would have to fetch is 400 remote_font_not_allowed, and anything over the size ceiling is 413 html_too_large. None of these cost a credit — the debit happens only once the document is about to be produced. If the render itself then fails for any reason, the credit is refunded automatically: a request that returns no document never costs anything.