HTML, Markdown or a URL in.
PDF bytes out.
PDFMint is a PDF generation API and an n8n community node. One POST with the content you already have — no template editor to learn, no template ID to paste, no second request to fetch the file.
Every plan includes password protection, headers and footers, page numbers, custom sizes and the n8n node.
# Markdown in, a real PDF file out.
curl -X POST https://pdf.mintapis.com/v1/pdf \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"markdown": "# Invoice 1042\n\nThanks for your business.",
"options": { "margin": "18mm", "pageNumbers": true }
}' \
--output invoice.pdfconst res = await fetch('https://pdf.mintapis.com/v1/pdf', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PDFMINT_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
html: '<h1>Invoice {{number}}</h1><p>Due {{due}}</p>',
data: { number: '1042', due: '2026-09-01' },
strict: true, // fail loudly on a typo'd placeholder
options: { format: 'A4', margin: '18mm', pageNumbers: true },
}),
});
// The response IS the PDF. Nothing else to fetch.
const pdf = Buffer.from(await res.arrayBuffer());
console.log(res.headers.get('X-PDFMint-Pages'), pdf.length);// Paste this straight onto an n8n canvas (Ctrl+V).
{
"nodes": [
{
"name": "PDFMint",
"type": "n8n-nodes-pdfmint.pdfMint",
"typeVersion": 1,
"position": [640, 300],
"parameters": {
"operation": "pdf",
"source": "markdown",
"markdown": "=# Invoice {{ $json.number }}",
"output": "binary",
"fileName": "=invoice-{{ $json.number }}.pdf",
"options": { "margin": "18mm", "pageNumbers": true }
}
}
]
}Measured on the live service on 23 August 2026. PDFMint is new — these are the only numbers we have, so they are the only ones we quote.
One endpoint, four inputs
Send what you already have
POST /v1/pdf takes exactly one of these four fields. Send two and it tells you which two, instead of quietly picking one.
Your own markup
Full CSS control. printBackground is on by default, so your colours and shading survive the print pipeline.
Markdown, print-ready
Rendered with a stylesheet built for paper: table rows never split across a page break, headings never orphan, code blocks wrap.
A public page
Wait for a delay, a CSS selector or networkidle before capture. Private and link-local addresses are refused.
A saved template
Addressed by name — "template": "invoice", not a hex id. Get the name wrong and the error lists the names you do have.
n8n community node
The file comes back on the node
Install n8n-nodes-pdfmint, paste your API key once, and the PDF arrives as binary data on the PDFMint node itself. Self-hosted n8n for now — n8n Cloud does not permit unverified community nodes, and ours is still in review. On Cloud, use the HTTP API with an HTTP Request node in the meantime.
-
No second HTTP Request node. The node calls
prepareBinaryData, so the file is attached to the item and ready for Gmail, Drive, S3 or Slack. - Templates are a dropdown. The node loads the templates on your account, so you pick a name instead of pasting an id.
-
usableAsTool: true. An n8n AI Agent can call PDFMint directly as a tool. Neither the CraftMyPDF nor the pdforge node declares this. - Four operations. Generate PDF, Generate Image, Merge PDFs and Get Usage — the same account, the same credential.
The node also puts pages, durationMs, creditsRemaining and any warning on the item’s JSON, next to the binary — so a downstream IF node can act on them.
The details that usually bite
Chrome’s sharp edges, filed down
Most of what goes wrong with headless-Chrome PDFs goes wrong silently. These are handled in the renderer, not left in your lap.
Page numbers that fit
Chrome clips a footer that does not fit inside the page margin. Set pageNumbers: true and the bottom margin is widened for you, so “Page 2 of 7” is actually on the page.
Headers that are not blank
Chrome renders header and footer templates in their own document at font-size 0, which is why they so often come out empty. Your markup is wrapped in a readable container aligned to the page margins — and laid out in the renderer first, so the margin is sized to the header you actually wrote and a letterhead with a logo in it does not print over your first paragraph.
Placeholders that speak up
A placeholder your data does not fill is reported in X-PDFMint-Warning, or refused outright with "strict": true. A key typo never ships as a blank invoice.
Passwords on every plan
Send "password" and the PDF comes back AES-256 encrypted via qpdf, with separate owner password and print/copy permissions. Included on the free tier too.
Type that renders
Inter, JetBrains Mono, Noto CJK and Noto Color Emoji ship inside the container. Chinese, Japanese, Korean and emoji come out as glyphs, not tofu boxes.
Renders that wait
waitFor takes milliseconds, a CSS selector or "networkidle" — so a chart that draws itself after load is in the PDF instead of half-painted.
SSRF-guarded fetches
Every URL you hand to url or merge has its DNS resolved before we fetch it, so a hostname pointing at 169.254.169.254 is caught too. Private, loopback and link-local addresses are refused, as are file: and data:.
Errors you can act on
Every error carries a machine code, a plain-English message, a hint and a link to the exact docs anchor. A failed render is refunded, so you are not billed for it.
Paper you control
A0–A6, Letter, Legal, Tabloid, Ledger or an exact width and height. Landscape, per-side margins, scale, page ranges, screen media and @page sizes.
The rest of the API
Five endpoints, one key
The same pm_live_… bearer token everywhere. Every response tells you what it cost you.
Generate a PDF
Bytes by default. Or "output": "url" for an expiring hosted link (one hour by default, up to seven days), or "base64" when your platform insists on JSON.
Generate a PNG or JPEG
The same inputs, rendered as an image. Full-page or a fixed viewport, retina scale factor, optional transparent background.
Merge up to 50 PDFs
Public URLs, base64, or a mix of both. Optionally rewrite the title and author metadata on the joined document.
Templates and usage
Store reusable HTML under a name you choose, with its own default page options. GET /v1/me returns your plan, credits used and remaining, and the reset date.
| Limit | Value | What happens at the edge |
|---|---|---|
| Request body | 12 MB | 413 with the size in the message |
| Rendered HTML | 10 MB | Refused, with a hint about inline base64 images |
| Render timeout | 30 s default, 120 s max | Set "timeout" per request |
| Merge inputs | 50 files | Refused with the count you sent |
| Hosted file | 20 MB, 7 days max | Binary output has no size limit |
How it compares
Measured against the alternatives
Everything below is taken from the vendors’ own published specs, pricing pages and n8n node source. Check it yourself — the sources are named under the table.
| PDFMint | CraftMyPDF | pdforge | |
|---|---|---|---|
| Raw HTML → PDF endpoint | Yes — html, markdown, url or template |
None. Every PDF endpoint in their OpenAPI spec requires a template_id |
Yes — a separate /html-to-pdf endpoint alongside its templates |
| Documents for $29/month | 50,000 ($0.00058 each) | 1,200 ($0.0242 each) | 1,000 for $35 ($0.035 each) |
| n8n node returns the file | Binary on the same node | Yes, when export_type is set to file |
URL only — the node never calls prepareBinaryData |
| Callable by an n8n AI Agent | usableAsTool: true |
Not declared | Not declared |
| Max payload | 12 MB | 4 MB | — |
| Max render timeout | 120 s | 100 s | — |
Scroll the table sideways to see every column.
Sources: CraftMyPDF’s published OpenAPI specification (craftmypdf_api.yaml) and pricing page; pdforge’s pricing page and the source of its n8n node; PDFMint’s own source and plan table. Dashes mean we did not find a published figure and would rather leave the cell empty than guess. These are good products with a different bet — a hosted drag-and-drop template designer. If that is what you want, buy that. PDFMint is for people who already have the HTML.
Pricing
One credit per document
A PDF, an image or a merge costs one credit. Failed renders are refunded automatically. Credits reset on the first of the month.
Enough to paste the first curl, wire up the n8n node and see a real PDF. Not enough to run a workflow on.
Start freeFor a live product: order confirmations, invoices, weekly reports.
Choose StarterFor batch runs — a statement per customer, on the first of every month.
Choose ProFor pipelines that generate documents as a matter of routine.
Choose ScaleStart on Free with no card; 10 documents a month is the whole of it, so a workflow that runs daily needs Starter. Checkout runs on Stripe, where you can add a VAT ID if you need one on the invoice — optional, and ignorable if you do not. Upgrade or cancel from the dashboard whenever you like.
Questions
Before you sign up
Do I have to build a template first?
No. Templates are optional and exist only so you can stop pasting the same HTML. POST /v1/pdf with html, markdown or url works on the first call, before you have saved anything.
How big is the free tier?
10 documents a month, permanently, with no card. It is sized to prove the thing works — the first curl, the n8n node, a real PDF on disk — not to run a workflow on. Anything recurring needs Starter at $9 for 5,000 documents. It is a smaller free tier than several competitors give: CraftMyPDF and PDFShift both give 50 a month.
What exactly counts as one document?
One credit per successful call to /v1/pdf, /v1/image or /v1/merge, regardless of page count. If the render fails, the credit is refunded before the error reaches you. GET /v1/me and the templates endpoints are free.
How do I fill a document with data?
Put placeholders in your HTML, Markdown or template and send a data object: {{name}}, nested {{customer.city}}, loops with {{#items}}…{{/items}}, inverted sections with {{^items}}…{{/items}} and unescaped output with {{{html}}}. Values are HTML-escaped by default.
What happens if my data is missing a placeholder?
You are told. The response carries an X-PDFMint-Warning header naming each unresolved placeholder, and "strict": true turns that into a 4xx error instead. A blank document is never returned silently.
Can I get a URL instead of the bytes?
Yes — "output": "url" returns a hosted link that expires after an hour by default, and up to seven days if you ask. The default is "binary", because in n8n and in most scripts the file is what you actually wanted.
Where do I install the n8n node?
Self-hosted n8n only, for now. n8n does not allow unverified community
nodes on n8n Cloud. PDFMint's node is submitted for verification and sitting in n8n's automated
review; until that clears, Cloud cannot install it. On a self-hosted instance:
Settings → Community nodes → Install, enter n8n-nodes-pdfmint,
add a PDFMint credential with your API key, and the node appears in the node panel. It is
published with an npm provenance attestation, and the source is on
GitHub.
On n8n Cloud the HTTP API works today — an HTTP Request node pointed at
/v1/pdf returns the same PDF bytes.
Your first PDF is about 30 seconds away
Create an account, copy the key off the dashboard, paste the curl above. 10 documents a month, free, no card.