Make has no built-in "HTML to PDF" module, but its HTTP → Make a request module can call a PDF API and pass the file to the next module. This recipe builds an invoice or report from data in earlier modules, prints it to an A4 PDF in Chromium, and saves it to Google Drive or attaches it to an email. Our test invoice (one A4 page, 20 KB) rendered in under 0.3 s.
What was tested: every request and JSON body on this page was sent to the live API and the PDFs were checked with pdfinfo. We did not run a Make scenario ourselves; module and field names are taken from Make's HTTP app documentation (October 2026).
A key gives 25 free renders a month, no signup. Either click Get a free key, or run a throwaway HTTP module once with Method POST and URL https://shotmint.creasework.com/v1/keys/free, then copy key from the output:
{
"key": "sm_...",
"credits": 25,
"usage": "Send the key as the X-API-Key header (or ?access_key=). 1 credit per render, failed renders are free; ...",
"topup_url": "https://shotmint.creasework.com/pricing#topup=sm_..."
}
Then delete that module. Don't leave it in the scenario: one free key per IP per day, so the second run would get a 429.
Add HTTP → Make a request after the module that produces your data (a Google Sheets row, an Airtable record, a webhook, a Stripe payment) and set:
| Field | Value |
|---|---|
| URL | https://shotmint.creasework.com/v1/pdf |
| Method | POST |
| Headers | X-API-Key: your key |
| Body content type | application/json |
| Body input method | Data structure fields: html (text), format, margin, print_background |
| Parse response | No |
| Return error if HTTP request fails | Yes |
| Timeout (advanced) | 60 seconds or more |
The body the module sends looks like this, with your mapped values inside the HTML:
{
"html": "<h1>Invoice INV-1042</h1><p>Billed to: Acme \"Rocket\" Corp</p><table>...</table>",
"format": "A4",
"margin": "15mm",
"print_background": true
}
The response is the PDF itself. The module's Data output holds the file bytes, and it is what you map into the next module.
With Body input method JSON string, Make pastes mapped values into your text as-is. A customer called Acme "Rocket" Corp, or a description with a line break, ends the JSON string early and the whole body becomes invalid. The API then answers 400 with this, and the scenario stops:
{
"error": "Request body is not valid JSON: Expected ',' or '}' after property value in JSON at position 55 ...",
"hint": "A value with an unescaped quote or line break breaks the JSON. In Make, use Body input method \"Data structure\" (or a Create JSON module) instead of a JSON string; ..."
}
A Data structure (or a JSON → Create JSON module whose output you map into the body) escapes quotes and line breaks for you, so it works on whatever text your earlier modules produce. If you must use a JSON string, keep mapped values out of it and pass only fixed text.
Write the template in a Text aggregator or a Set variable module, map your fields into it, and map that into the html field. Keep 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 {{1.number}}</h1>
<p>Billed to: {{1.customer}}<br>Date: {{formatDate(now; "YYYY-MM-DD")}}</p>
<table>
<tr><th>Item</th><th>Qty</th><th>Price</th></tr>
{{2.text}}
</table>
<p><b>Total: {{1.total}}</b></p>
For line items, put an Iterator over the items and a Text aggregator that outputs one <tr> per item (that is {{2.text}} above). Images must be absolute https:// URLs; web fonts load from Google Fonts like on a normal page. Useful extras: "landscape": true, "format": "Letter", "page_numbers": true for a centered "1 / 3" footer, or header_template / footer_template for your own. To print an existing page instead of HTML, send "url": "https://..." in place of html.
{{1.number}}.pdf, and map the HTTP module's Data into Data.Always type the file name yourself and end it with .pdf: the HTTP module doesn't take one from the response, and some targets refuse a file without an extension.
With "Return error if HTTP request fails" on, an error stops the run instead of uploading an error message as a broken PDF, and Make shows the JSON reason. Add an error handler route if you want to be notified.
| Status | Meaning and fix |
|---|---|
400 | Bad body or parameter read the error text; usually invalid JSON (see above) |
401 | Missing or wrong key check the X-API-Key header has no spaces |
402 | Out of credits the body has a top-up link; €5 buys 2,000 PDFs that never expire |
503 | Queue full during a burst safe to retry; process bundles sequentially |
Failed renders cost nothing. One PDF per invoice is 1 credit, so 100 invoices a month runs for 20 months on one €5 pack. Check the balance any time with a GET to https://shotmint.creasework.com/v1/account using the same header.
Screenshots work the same way: GET https://shotmint.creasework.com/v1/screenshot with a url query parameter, see n8n, Make and Zapier screenshots. For HTML-to-PDF in code, see Node.js or Python, and 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