Convert HTML to PDF with curl
Turn HTML and CSS into a PDF: invoices, receipts, reports, tickets. Rendered by Chromium, so modern CSS (flexbox, grid, web fonts) works. Here is the whole call with curl, with the output it produced when we ran it.
Before you start
- An API key. Create a free account; it comes with free tokens every month. Put the key in an environment variable so it stays out of your code:
export BLOODWEB_KEY=your_key - curl 7.76 or later (for
--fail-with-body, which makes curl stop on an error and still show the reason). - The input file in the same folder:
invoice.html. Use your own; that is the name the code expects.
The code
Save it as html-to-pdf.sh and run bash html-to-pdf.sh.
curl -sS --fail-with-body \
-H "Authorization: Bearer $BLOODWEB_KEY" \
--data-urlencode html@invoice.html \
-d format=A4 \
https://bloodweb.net/api/v1/html-to-pdf \
-o invoice.pdf
What you get
Tested 19 October 2026: this exact code ran against our test copy of the API, which runs the same service code as bloodweb.net, and produced this.
It writes invoice.pdf: a PDF, 1 page, 24 KB.
Change the call
Every option the HTML to PDF takes. Add or change them the same way as the ones in the code.
html: The HTML. It must be self-contained: inline <style>, and images and fonts as data: URIs. Remote files and scripts are not loaded.format: Paper size. When format, landscape and margin are all left out, the page's own CSS @page rule decides. One of: A3, A4, A5, Letter, Legal, Tabloid.landscape: Landscape orientation.margin: Page margin in millimetres.background: Print background colours and images.page_numbers: Print "Page 1 of 3" at the bottom centre of every page.
If it fails
A failed call is not charged. The answer is JSON with an error object; curl stops with an error, and that JSON lands in invoice.pdf instead of the result, so open it as text to read the reason. The usual ones:
- 401
missing_keyorinvalid_key:BLOODWEB_KEYis not set in the shell that runs the code, or the key was revoked. - 400
bad_param: a value is wrong or missing. The message names the parameter. - 402
insufficient_tokens: the balance does not cover the call. Top up in the dashboard. - 429
rate_limited: too many calls at once. Wait the seconds in theRetry-Afterheader and send it again.
Every code and its fix is in the docs.
Next
- The HTML to PDF page: every option, the limits, and Try it now in your browser.
- Getting started: keys, errors and rate limits for all the services.