Every billing feature eventually hits the same wall: "now turn this order into a PDF the customer can download."
The usual options all have a tax:
- Headless Chrome / Puppeteer — you now run and patch a browser in production. Memory spikes, zombie processes, font issues in Docker.
- wkhtmltopdf — unmaintained, CSS from 2015.
- A LaTeX/HTML template engine — you own the templates, the rendering, and every edge case forever.
For a feature that's supposed to be a side-quest, that's a lot of infrastructure.
I got tired of re-solving this on every project, so I built DocForge — a stateless HTTP API. You POST structured JSON, you get back PDF bytes. That's the whole integration.
The one call
import requests
resp = requests.post(
"https://pdf44.p.rapidapi.com/v1/documents/invoice",
headers={
"content-type": "application/json",
"X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY",
"X-RapidAPI-Host": "pdf44.p.rapidapi.com",
},
json={
"invoice_number": "2026-0001",
"issue_date": "2026-06-17",
"currency": "USD",
"seller": {"name": "Rapid Labs", "email": "billing@rapidlabs.dev"},
"client": {"name": "Acme Inc."},
"items": [
{"description": "Pro plan (annual)", "quantity": 1, "unit_price": 590.0},
{"description": "Onboarding", "quantity": 2, "unit_price": 75.0},
],
"tax_rate": 8.5,
},
)
resp.raise_for_status()
open("invoice.pdf", "wb").write(resp.content)
That's it. No browser in your container, no template to host. The response is application/pdf bytes — stream it to the user, drop it in S3, or attach it to an email.
Node, same idea
import { writeFileSync } from "node:fs";
const res = await fetch("https://pdf44.p.rapidapi.com/v1/documents/invoice", {
method: "POST",
headers: {
"content-type": "application/json",
"X-RapidAPI-Key": process.env.RAPIDAPI_KEY,
"X-RapidAPI-Host": "pdf44.p.rapidapi.com",
},
body: JSON.stringify({
invoice_number: "2026-0001",
issue_date: "2026-06-17",
currency: "USD",
seller: { name: "Rapid Labs" },
client: { name: "Acme Inc." },
items: [{ description: "Pro plan", quantity: 1, unit_price: 59 }],
}),
});
writeFileSync("invoice.pdf", Buffer.from(await res.arrayBuffer()));
It's not just invoices
Same JSON-in / PDF-out shape, six document types:
| Endpoint | What you get |
|---|---|
/v1/documents/invoice |
Line items, tax, discount, multi-currency |
/v1/documents/receipt |
Compact, store-branded |
/v1/documents/quote |
Estimates with validity dates |
/v1/documents/packing-slip |
Fulfilment doc, no prices |
/v1/documents/certificate |
Landscape, framed, signature lines |
/v1/render/html |
Bring your own HTML → PDF |
Currency formatting (USD, EUR, GBP, CNY, JPY) is built in, so you're not hand-rolling Intl.NumberFormat edge cases.
Why stateless matters
DocForge never stores your data — the request comes in, the PDF goes out, nothing is persisted. That means no data-retention policy to write, nothing to leak, and it scales horizontally by just running more containers. Under the hood it's Chromium for pixel-stable rendering, but you never have to operate that.
Try it
It's on RapidAPI with a free tier (50 PDFs/month) to kick the tires, and a paid plan when you need volume:
👉 https://rapidapi.com/tonypfwang-addP08RhJS3/api/pdf44
If you've been putting off the "generate the PDF" ticket, this is the version where it takes 15 minutes instead of a sprint. Happy to answer questions in the comments — what document type would you want next?
Top comments (0)