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

Step 1

Design it

Drag blocks onto the page in the editor, or start from a ready-made starter.

Step 2

Variable-ize it

Swap anything that changes per document for a {{ variable }}.

Step 3

Publish it

Snapshot the draft into an immutable version. The API serves only published versions.

Step 4

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.

AI assistant

The editor has an AI assistant that builds and changes templates in conversation. Open it with the AI assistant button in the editor, or choose Create with AI on your templates list to start from a blank page.

Describe what you need, or give it a document you already use: paste a screenshot, drag in an image, or attach a PDF (up to 5 files per message, 10 MB each; PNG, JPEG, WebP, GIF or PDF). It builds the template, renders it, looks at the pages, and fixes what’s off before it replies. Then keep chatting to refine it: “make the header navy”, “add a VAT column”, “move the logo to the right”.

It works out the data contract

Anything that changes per document becomes a variable, and repeating rows become a table bound to a list. Values from your reference become the sample data, so the preview looks like the original.

You see every change

Changes appear in the editor as they’re made. The editor is read-only while the assistant works, so your edits and its edits never collide.

Undo the last change

Undo puts the template back to how it was before the assistant’s last turn, as long as nobody has edited it since.

It never publishes

All changes go to your draft. You publish from the editor when you’re happy, so your live integration is never affected.

What to expect when rebuilding a document

  • Layout, structure, colours, tables and the data fields match closely.
  • Typography is approximate: templates use one font family throughout, a text block has one style (bold or not), and line height isn’t adjustable. The assistant tells you what it couldn’t reproduce.
  • Logos in a screenshot can’t be cut out. Attach the logo as its own image and the assistant will use it, or it will leave an image variable you fill in per document.
  • Templates don’t calculate. Totals and other derived values come from your data.

The assistant uses Anthropic’s Claude. Your messages, attachments and the template are sent to Claude to do the work; attachments expire from Anthropic’s storage after 30 days. Using it needs permission to edit templates (organization owners and admins).

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.

Text

Headings and paragraphs, with {{ variable }} tokens anywhere inside.

Image

A fixed or variable image — upload one, or pass a URL or data URI at render time.

Barcode

QR codes, Code 128, EAN-13, Data Matrix and PDF417, from fixed text or a variable.

Table

One row per item in a list variable; the header repeats across pages.

Row

Holds columns for side-by-side layout (e.g. logo left, address right).

Section

A container. Can repeat over a list, or fill the page to centre content.

Divider

A horizontal rule between blocks.

Spacer

Fixed vertical gap, in millimetres.

Page break

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.

string

Any text — names, addresses, notes.

number

Amounts, quantities, totals.

date

Dates you interpolate into the document.

boolean

True/false, for showing or hiding content.

image

A URL or data URI rendered as an image.

list

An 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_here

Permissions

Each key has permissions, so it can do only what it’s for. New keys can render and read templates; turn on the others when a key needs them. A request a key isn’t allowed to make gets a 403 naming the missing permission.

Render PDFs
render

Render published templates and markdown. On for new keys.

Read templates
templates:read

List templates and read their data contracts. On for new keys.

Edit templates
templates:write

Create templates and change drafts. AI clients need this to design templates.

Publish templates
templates:publish

Publish drafts as new versions the render API serves. Give it only to keys that should go live.

Read usage
usage:read

See render counts, failures and remaining quota.

To change a key’s name or permissions, use the pencil next to it on the API Keys page. Changes apply to the key’s very next request, and the key itself stays the same, so there’s nothing to roll out.

Keep keys server-side. Anyone holding a key can do whatever its permissions allow, including spending your render allowance.

AI clients (MCP)

SnappyPDF runs a Model Context Protocol (MCP) server, so AI coding assistants such as Claude Code and Cursor can design and edit your templates, check the rendered pages, and report on usage, from wherever you already work. Share a screenshot with your assistant and ask it to rebuild the document as a Snappy template.

The server is at https://snappypdf.co/api/mcp and authenticates with an organization API key. Create one under API Keys with the permissions the assistant should have: usually Edit templates, plus Read usage for usage questions. Add Publish templates only if it should be able to make versions live.

Connect Claude Code

claude mcp add --transport http snappy-pdf https://snappypdf.co/api/mcp \
  --header "Authorization: Bearer spdf_your_key_here"

When you create a key with Edit templates, the API Keys page shows this command with your key already filled in.

Connect Cursor

Add the server to .cursor/mcp.json in your project (or ~/.cursor/mcp.json for every project):

{
  "mcpServers": {
    "snappy-pdf": {
      "url": "https://snappypdf.co/api/mcp",
      "headers": {
        "Authorization": "Bearer spdf_your_key_here"
      }
    }
  }
}

Any MCP client that supports Streamable HTTP with a custom header works the same way. Claude.ai and Claude Desktop connectors need OAuth sign-in, which isn’t available yet.

Available tools

get_authoring_guide
any key

The guide to Snappy’s template format: blocks, layout, variables, edit ops and examples. Assistants read it before building anything. Also available as the snappy://authoring-guide resource.

list_templates
Read templates

Your organization’s templates, drafts included, with status and published version.

get_template
Read templates

A template’s draft (or a published version) in full: blocks with their ids, variables, sample data and page settings, plus an outline and lint warnings.

create_template
Edit templates

Creates a new draft from a short template description format. Variables are worked out from the {{ tokens }} used.

update_template
Edit templates

Changes a draft with small edits addressed by block id (insert, update, move, remove, page and header/footer settings, sample data). All of a batch applies, or none of it.

preview_template
Read templates

Renders the draft and returns the pages as images, exactly as the render API would produce them, so the assistant can check its work.

import_image
Edit templates

Copies an image from a public https link (PNG, JPEG or WebP, up to 5 MB) into Snappy for use in a template, such as your logo.

publish_template
Publish templates

Publishes the draft as a new version. Assistants are told to do this only when you ask.

get_usage
Read usage

Render counts, error rate and speed, failures by error code, per-template and daily breakdowns, and the quota left right now.

Good to know

  • Every change goes to a draft. Nothing reaches your render API until a template is published.
  • Previews count against your organization’s editor render allowance, never the API allowance your integration uses. Reading templates and usage is free.
  • When a request isn’t allowed or something is wrong with it, the assistant gets a plain message saying why (for example a missing permission, or exactly which field of a template is invalid) and can usually fix it itself.
  • Ask in plain words. Things that work well:
    • “Build me an invoice template like this one [screenshot]”
    • “In the delivery note, add a column for batch numbers and make the heading navy”
    • “Preview the certificate and fix anything that overflows the page”
    • “How many renders did the invoice template do this month, and why are some failing?”

Discovery API

Two read-only endpoints let an integration find your templates and learn their data contract at runtime, instead of you hard-coding ids and field names. They’re what makes it possible to build a “pick a template, map your fields” UI on top of SnappyPDF.

Both authenticate with the same API key as the render API, and neither counts against your render allowance — that budget exists to protect the PDF renderer, which these never touch.

List your templates

GET /api/v1/templates returns every template with a published version — exactly the ones the render API will serve.

GET /api/v1/templates?search=invoice&page=1&pageSize=50
Authorization: Bearer spdf_your_key_here

→ 200 · application/json
{
  "data": [
    {
      "id": "6512f3a9c4d21b0f7e8a4c19",
      "name": "Invoice",
      "status": "active",
      "publishedVersion": 3,
      "lastPublishedAt": "2025-11-04T09:12:44.001Z",
      "pageSize": "A4",
      "orientation": "portrait"
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 50
}
page

Optional. Defaults to 1.

pageSize

Optional. Defaults to 50, maximum 100.

search

Optional. Case-insensitive match on the template name.

Templates you’ve never published don’t appear, since there’d be nothing to render. Archived ones do — they still render, and quietly hiding them would make this list disagree with the render API. Use status if you want to filter or grey them out.

Fetch a template’s data contract

GET /api/v1/templates/:id returns the variables a render payload should provide, plus the template’s sample data — a payload that’s guaranteed to render, so you can test the wiring before you have real data. Pass ?version= to read the contract of a specific published version; omit it for the latest.

GET /api/v1/templates/6512f3a9c4d21b0f7e8a4c19?version=3
Authorization: Bearer spdf_your_key_here

→ 200 · application/json
{
  "data": {
    "id": "6512f3a9c4d21b0f7e8a4c19",
    "name": "Invoice",
    "version": 3,
    "publishedAt": "2025-11-04T09:12:44.001Z",
    "pageSize": "A4",
    "orientation": "portrait",
    "variables": [
      { "name": "clientName", "label": "Client name", "type": "string" },
      { "name": "total", "type": "string" },
      {
        "name": "lineItems",
        "type": "list",
        "itemSchema": [
          { "name": "description", "type": "string" },
          { "name": "qty", "type": "number" },
          { "name": "price", "type": "string" }
        ]
      }
    ],
    "sampleData": {
      "clientName": "Acme Studio",
      "total": "$1,130.50",
      "lineItems": [
        { "description": "Design", "qty": 2, "price": "$400.00" }
      ]
    }
  }
}

If you pin a version when rendering, read the contract for that same version — a later publish can add or rename variables, and you want the contract that matches the document you’re actually producing.

The response describes your data contract, not the design. Block layout, styling and page setup stay internal to the editor, so we can keep improving them without breaking your integration.

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/pdf
templateId

Required. The template to render.

version

Optional. Pin a published version; omit for the latest.

data

Optional. 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.

Markdown to PDF

No template needed: POST /api/v1/render-markdown takes Markdown and returns a typeset PDF, styled by one of ten built-in themes. It is the keyed version of the free converter — same renderer, same options, but metered against your organization’s render quota instead of the anonymous per-IP limit, and counted in your usage dashboard.

POST /api/v1/render-markdown
Authorization: Bearer spdf_your_key_here
Content-Type: application/json

{
  "markdown": "# Release notes\n\nShipped **v2.4** today…",
  "theme": "technical",
  "pageSize": "A4",
  "margin": "normal",
  "pageNumbers": true,
  "accent": "#0284c7",
  "font": "Inter",
  "headingFont": "Space Grotesk"
}

→ 200 · application/pdf
markdown

Required. CommonMark + tables, up to 200,000 characters. Raw HTML is escaped.

theme

clean, academic, technical, editorial, minimal, contract, brutalist, swiss, newspaper, midnight. Default clean.

pageSize · margin

A4 or Letter; narrow, normal or wide.

pageNumbers

Boolean. Adds a “1 / n” footer on every page.

accent

Hex colour for rules, links and highlights. Defaults to #ec4899.

font · headingFont

Google Fonts family names from the supported list: Inter, Roboto, Open Sans, Lato, Montserrat, Poppins, Nunito, Work Sans, DM Sans, IBM Plex Sans, Space Grotesk, Source Sans 3, Merriweather, Lora, Playfair Display, Libre Baskerville, EB Garamond, Crimson Text, IBM Plex Serif, Source Serif 4, Bitter, Oswald, Raleway, Josefin Sans. Omit either to use the theme’s own face.

Error states

Errors come back as standard HTTP status codes with a message. The common ones — 400, 401, 403 and 404 apply to every endpoint; the rest are specific to rendering:

400Invalid request

The request failed validation — usually a missing templateId, malformed data, or an out-of-range query parameter. The message lists each problem as path: problem, and data.issues has the full list.

401Auth failed

No API key was sent, or the key is unknown/revoked. Send it as a Bearer token or x-api-key header.

403Missing permission

The key is valid but lacks the permission this endpoint needs (e.g. Render PDFs). Keys created before permissions existed can render and read templates.

404Not found / not published

The templateId does not exist, or it exists but has no published version (publish it in the editor first).

429Rate limited

You exceeded the per-minute or monthly render allowance. Check the Retry-After and X-RateLimit-* headers, then back off.

500Render failed

The template rendered but PDF generation errored. Retry; if it persists, get in touch.

503Capacity exhausted

The render pool is momentarily saturated. The template is fine — retry after a short delay.

401, 403, 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.

Renders

Total successful API renders in the period, and the volume of PDF generated.

Error rate

Share of requests that failed, with the raw failed count.

Latency

p95 and average render time, so you can see the slow tail, not just the average.

Rate limited

How many requests were rejected with a 429 — a sign you’re near your allowance.

By template

Renders, failures, latency and last-render time broken down per template.

By error

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