Docs
SnappyPDF turns a template plus JSON into a PDF. Design the document once in the editor, mark the parts that change as variables, publish it, then render it by API. Here’s the whole loop.
Overview
Design it
Drag blocks onto the page in the editor, or start from a ready-made starter.
Variable-ize it
Swap anything that changes per document for a {{ variable }}.
Publish it
Snapshot the draft into an immutable version. The API serves only published versions.
Render it
POST your JSON with an API key and get the finished PDF back on the same request.
Create a template
From Templates, create a new template (or duplicate a starter) and open the editor. The editor has three parts: a toolbox of blocks on the left, the page canvas in the middle, and a properties panel on the right for whatever block is selected.
Drag blocks onto the page and arrange them. Set the page size and orientation for the whole document, and optionally turn on a header and footer band that repeat on every page. Use the Preview to see the exact rendered output with sample data at any time — the preview and the final PDF come from the same renderer, so what you see is what you get.
When it’s ready, hit Publish. Publishing takes an immutable snapshot — a numbered version — and the render API only ever serves published versions. That means you can keep editing the draft without ever breaking a live integration. Publish again to cut a new version; pin a specific version from the API when you need reproducible output.
How layout works
SnappyPDF uses flow layout, not absolute positioning. Blocks stack top to bottom and flow onto new pages as content grows. There are no x/y coordinates — which is exactly what lets a document with ten line items and one with ten thousand both paginate correctly without you touching the design.
Dimensions like page size, margins and spacing are in millimetres; font sizes are in points — the same units you’d use in print.
Headings and paragraphs, with {{ variable }} tokens anywhere inside.
A fixed or variable image — pass a URL or data URI at render time.
One row per item in a list variable; the header repeats across pages.
Holds columns for side-by-side layout (e.g. logo left, address right).
A container. Can repeat over a list, or fill the page to centre content.
A horizontal rule between blocks.
Fixed vertical gap, in millimetres.
Forces everything after it onto a new page.
Repeating content
Two blocks turn a list variable into repeating output. A table renders one row per item, and its header row repeats automatically when the table spills onto the next page. A section set to repeat re-renders its whole contents once per item — useful for cards, grouped blocks or anything more elaborate than a table row. Inside either, you reference the current item’s fields directly (see Variables below).
Headers & footers
Header and footer bands are fixed-height (in mm) and render on every page. They take a restricted set of blocks — no tables or page breaks, since a fixed band can’t paginate. They’re the only place page counters work: drop {{ page }} and {{ pages }} to get “Page 1 of 4”.
Variables
A variable is any part of the document that changes between renders. Define your variables in the Variables panel — each has a name and a type. A template’s variables are its data contract: they’re exactly the fields your API payload provides.
Names use letters, numbers and underscores and must start with a letter. Reference a variable anywhere text is allowed with a {{ token }}. Values are HTML-escaped, and you can reach into nested data with dot paths like {{ client.name }}.
Tip: type “/” to insert a variable
In any content input, press / to pop up a menu of every variable on the template. Keep typing to filter it, use the arrow keys to move, and press enter (or click) to drop the token in at your cursor — no need to remember exact names or type the braces yourself.
stringAny text — names, addresses, notes.
numberAmounts, quantities, totals.
dateDates you interpolate into the document.
booleanTrue/false, for showing or hiding content.
imageA URL or data URI rendered as an image.
listAn array of items — each with its own typed fields — for tables and repeating sections.
A list variable holds an array of items, and you give each item its own typed fields (say description, qty, price). Bind a table or a repeating section to the list; inside it, refer to the current item’s fields — the item and its index are in scope for each iteration.
Every variable gets sample data so the editor preview shows a realistic document before you’ve wired up any real payload. The sample data also becomes the fallback the API renders when a request omits data.
API keys
The render API authenticates with an organization API key. Create one under API Keys in your organization settings (owners and admins only). The full key is shown once, at creation — we only ever store a hash of it, so copy it then and keep it somewhere safe. If you lose it, revoke it and make a new one.
Keys are prefixed spdf_. Send yours on every request as either header:
Authorization: Bearer spdf_your_key_here
# — or —
x-api-key: spdf_your_key_hereKeep keys server-side. Anyone holding a key can render any of your organization’s published templates and spend its render allowance.
The render API
POST /api/v1/render takes a small JSON body and returns the finished PDF bytes (application/pdf) on the same request — no polling, no webhooks.
POST /api/v1/render
Authorization: Bearer spdf_your_key_here
Content-Type: application/json
{
"templateId": "6512f3a9c4d21b0f7e8a4c19",
"version": 3,
"data": {
"clientName": "Acme Studio",
"total": "$1,130.50",
"lineItems": [
{ "description": "Design", "qty": 2, "price": "$400.00" }
]
}
}
→ 200 · application/pdftemplateIdRequired. The template to render.
versionOptional. Pin a published version; omit for the latest.
dataOptional. Values for your variables; falls back to the template’s sample data.
Every response carries rate-limit headers so integrations can self-throttle: X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset for the per-minute window, plus -Month variants for the monthly window. During the free beta the defaults are 10 renders/minute and 1,000 renders/month per organization.
Error states
Errors come back as standard HTTP status codes with a message. The common ones:
400Invalid requestThe JSON body failed validation — usually a missing templateId or malformed data.
401Auth failedNo API key was sent, or the key is unknown/revoked. Send it as a Bearer token or x-api-key header.
404Not found / not publishedThe templateId does not exist, or it exists but has no published version (publish it in the editor first).
429Rate limitedYou exceeded the per-minute or monthly render allowance. Check the Retry-After and X-RateLimit-* headers, then back off.
500Render failedThe template rendered but PDF generation errored. Retry; if it persists, get in touch.
503Capacity exhaustedThe render pool is momentarily saturated. The template is fine — retry after a short delay.
401, 404 and 429 mean fix the request (key, template, or back off). 500 and 503 are safe to retry — the template itself is fine.
Usage dashboard
Your organization’s Usage page reports API render activity over the last 7, 30 or 90 days. Editor previews and Download-PDF clicks are metered separately and never counted against your API numbers.
Total successful API renders in the period, and the volume of PDF generated.
Share of requests that failed, with the raw failed count.
p95 and average render time, so you can see the slow tail, not just the average.
How many requests were rejected with a 429 — a sign you’re near your allowance.
Renders, failures, latency and last-render time broken down per template.
A count of each failure type (not found, not published, invalid request, and so on).
A Beta allowance panel shows how much of your monthly and per-minute render budget is left and when each window resets, so you can see the ceiling before you hit it. Render history is retained for 90 days.
Stuck on something not covered here? Email us — we’re happy to help.
Stop hand-crafting PDFs
Your first template takes five minutes. The ten-thousandth PDF takes a fraction of a second.
Try it free