Zapier has no built-in "HTML to PDF" action, and its Webhooks step handles JSON much better than raw file bytes. The fix is to ask the PDF API for a link instead of the file: send your HTML with response=url, get back JSON with a url, and map that URL into the File field of Google Drive, Gmail, Slack or Dropbox. Zapier downloads the PDF from it. A one-page invoice took 0.15 s to render.
What was tested (October 2026): every request on this page was sent to the live API in the shapes Zapier sends (JSON, form fields, a wrapped array, a raw body), the PDFs were checked with pdfinfo, and the Code step below was run in Node 22 with Zapier's inputData and fetch stood in. We did not run a Zap ourselves; field names and plan limits come from Zapier's help center.
A key gives 25 free renders a month, no signup: click Get a free key and copy it. One PDF is one credit, and failed renders are free.
Add an action step after your trigger (a new Google Sheets row, a Stripe payment, a form entry), pick Webhooks by Zapier, event POST, and fill in:
| Field | Value |
|---|---|
| URL | https://shotmint.creasework.com/v1/pdf |
| Payload Type | jsonform works too, see below |
| Data | html: your HTML, with trigger fields mapped inresponse: urlfilename: e.g. invoice- + the invoice numberformat: A4 (or Letter)margin: 15mm |
| Headers | X-API-Key: your key |
Leave Wrap Request In Array, File and Basic Auth empty. Click Test step and the step returns:
{
"url": "https://shotmint.creasework.com/f/page-oMbpcO5M5qsbMhe_46ekdA.pdf",
"content_type": "application/pdf",
"size": 14524,
"expires_at": "2026-10-23T23:02:15.748Z",
"credits_remaining": 49
}
The PDF behind url is kept for 24 hours under an unguessable name, which is plenty for the next steps of the same Zap. Zapier builds the JSON from the Data fields itself, so a customer called Acme "Rocket" Corp or an address with line breaks can't break the request.
The API accepts each way people tend to set this step up, so a Zap that "almost" matches still works:
true, yes, 1 all count as true for options like landscape.400 Request body is not valid JSON. Use POST with Data fields instead, which escapes them for you.Webhooks by Zapier is not on the Free plan. A Code by Zapier → Run JavaScript step can make the same call. Under Input Data add html (map your HTML) and number (for the file name), then paste:
// Code by Zapier (JavaScript). Input Data: html = your HTML, number = invoice number
const res = await fetch('https://shotmint.creasework.com/v1/pdf', {
method: 'POST',
headers: { 'X-API-Key': 'YOUR_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({ html: inputData.html, format: 'A4', margin: '15mm', response: 'url' }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${data.error} ${data.hint || ''}`);
return { pdf_url: data.url, file_name: `${inputData.number}.pdf`, credits_left: data.credits_remaining };
The step outputs pdf_url, file_name and credits_left for the next steps, and a bad key or a broken body stops the Zap with the API's reason instead of passing on an empty link. On the Free plan, Code steps may run for only 1 second. A one-page invoice from HTML took 0.15 s round trip from our server, so it fits; rendering a heavy live page with url instead of html can take 2-3 s and needs a paid plan (30 s).
Write the HTML straight into the html field (or Input Data) and insert trigger fields where they belong. Put the styles in a <style> tag at the top, since Chromium prints it like a web page:
<style>
body { font-family: sans-serif; margin: 0 }
table { width: 100%; border-collapse: collapse }
td, th { border-bottom: 1px solid #ddd; padding: 8px; text-align: left }
</style>
<h1>Invoice [Number]</h1>
<p>Billed to: [Customer name]<br>Date: [Date]</p>
<table>
<tr><th>Item</th><th>Qty</th><th>Amount</th></tr>
[Line item rows]
</table>
<p><b>Total: [Total]</b></p>
Each [...] is a field you insert from the trigger. For line items, a Formatter → Utilities → Line-item to Text step (or a Code step) can turn them into one <tr> per item. Images need absolute https:// URLs. Other useful Data fields: landscape = true, page_numbers = true for a "1 / 3" footer, or url = a page address in place of html to print an existing page.
url (or pdf_url) into File, and the file is named after the filename you sent (the URL ends in /invoice-1042.pdf and the download carries that name too). Without filename, set the name in the Drive step or it is saved as page.pdf.Screenshots work the same way: POST or GET https://shotmint.creasework.com/v1/screenshot with url and response=url, or put a screenshot URL straight into a File field, see screenshots in n8n, Make and Zapier.
| Status | Meaning and fix |
|---|---|
400 | Bad body or parameter the JSON reply says which; often a Custom Request body broken by a quote |
401 | Missing or wrong key check the header name is X-API-Key and the value has no spaces |
402 | Out of credits the reply has a top-up link; €5 buys 2,000 PDFs that never expire |
503 | Queue full during a burst safe to retry; Zapier's autoreplay can do it |
100 invoices a month is 100 credits, so one €5 pack lasts 20 months. Every reply includes credits_remaining, so a Filter step can warn you when it drops low. Using Make or n8n instead? See HTML to PDF in Make and HTML to PDF in n8n. Every option is in the API reference.
No signup, no card. Get a key in one click, then buy credits only if you need more (from €5 for 2,000, never expire). From a script, an AI agent, Make or Zapier, POST /v1/keys/free (no body) returns a key as JSON (openapi.json, no-code recipes).
All guides: guides · integrations · comparisons