DEV Community

Cover image for Generate invoice PDFs from JSON in one API call — no headless Chrome to babysit
TonyWang wa
TonyWang wa

Posted on • Originally published at rapid.aivgg.com

Generate invoice PDFs from JSON in one API call — no headless Chrome to babysit

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)
Enter fullscreen mode Exit fullscreen mode

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()));
Enter fullscreen mode Exit fullscreen mode

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)