Guide
JSON to invoice PDF API
POST https://pdfops.dev/api/invoice takes a JSON object with
the seller, the buyer and the line items, and returns a complete invoice
PDF: header, address blocks, items table, subtotal, tax, total and notes.
There is no template to design and no headless browser in the path, and
the same JSON always returns the same bytes. It is free to try with no
key: 100 requests per IP per month, or 250 with a free key.
The request
One JSON object. from, to and items are required; everything else is optional. Parties are a plain string or { name, lines } with up to 6 address lines.
curl -X POST https://pdfops.dev/api/invoice \
-H "Content-Type: application/json" \
-o invoice-INV-0042.pdf \
-d '{
"from": { "name": "Acme Studio LLC", "lines": ["100 Main St", "Portland, OR 97201"] },
"to": { "name": "Globex Corporation", "lines": ["Attn: Accounts Payable", "42 Industrial Way", "Springfield, IL 62701"] },
"invoice_number": "INV-0042",
"date": "2026-09-01",
"due": "2026-10-01",
"currency": "USD",
"tax_rate": 8.5,
"items": [
{ "description": "Design retainer - September", "quantity": 1, "unit_price": 2400 },
{ "description": "Landing page build", "quantity": 3, "unit_price": 650 },
{ "description": "Stock photo licences", "quantity": 12, "unit_price": 9.5 }
],
"notes": "Payment by bank transfer within 30 days. Thank you for your business."
}'
No API key is needed to try it. Add -H "X-API-Key: pdfops_live_..." to use your own quota.
The response
200 OK with Content-Type: application/pdf and the PDF as the body: one US Letter page, 3,311 bytes for this request. The text layer of that file, extracted with pdftotext -layout (spacing condensed to fit):
INVOICE INV-0042
Date: 2026-09-01
Due: 2026-10-01
FROM BILL TO
Acme Studio LLC Globex Corporation
100 Main St Attn: Accounts Payable
Portland, OR 97201 42 Industrial Way
Springfield, IL 62701
DESCRIPTION QTY UNIT AMOUNT
Design retainer - September 1 USD 2,400.00 USD 2,400.00
Landing page build 3 USD 650.00 USD 1,950.00
Stock photo licences 12 USD 9.50 USD 114.00
Subtotal USD 4,464.00
Tax (8.5%) USD 379.44
TOTAL USD 4,843.44
NOTES
Payment by bank transfer within 30 days. Thank you for your business.
Generated with pdfops.dev
Line amounts, subtotal, tax and total are computed for you. Long invoices continue onto further pages with the table header repeated, up to 100 items. The PDF's title metadata is Invoice INV-0042 and its creation date is the invoice date.
Validation errors come back as 400 JSON with a stable code, so a caller or an agent can fix the input and retry:
{"error":"invalid_items","details":"\"items\" must be a non-empty array"}
All codes, field limits and the Stripe webhook example are in the /api/invoice reference.
Same JSON, same bytes
Send the request above twice, or next month, and you get the same file:
$ sha256sum invoice-a.pdf invoice-b.pdf
9c7a206fe5a5bf2cbe22250c965b49e8ab8a55dc8167474afa0864a451e87326 invoice-a.pdf
9c7a206fe5a5bf2cbe22250c965b49e8ab8a55dc8167474afa0864a451e87326 invoice-b.pdf
That hash was measured on 2026-09-24 against both the production API and a local build of the same code. Most HTML-to-PDF renderers stamp the render time into the file, so every run hashes differently. Here there is no wall-clock input, which is what makes a retried billing webhook safe: store the PDF under its invoice number or hash and a duplicate delivery overwrites an identical file. The pattern is worked through in snapshot-testing PDFs with a hash.
From code
Python
from pdfops import PdfOps # pip install pdfops, zero dependencies
pdf = PdfOps().invoice({
"from": "Acme Studio LLC",
"to": {"name": "Globex Corporation", "lines": ["42 Industrial Way"]},
"invoice_number": "INV-0042",
"date": "2026-09-01",
"items": [{"description": "Landing page build", "quantity": 3, "unit_price": 650}],
})
open("invoice-INV-0042.pdf", "wb").write(pdf)
Node.js, Bun, Deno, Workers
const res = await fetch('https://pdfops.dev/api/invoice', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
from: 'Acme Studio LLC',
to: 'Globex Corporation',
invoice_number: 'INV-0042',
date: '2026-09-01',
items: [{ description: 'Landing page build', quantity: 3, unit_price: 650 }],
}),
});
if (!res.ok) throw new Error(JSON.stringify(await res.json()));
const pdf = new Uint8Array(await res.arrayBuffer());
The typed client pdfops-sdk wraps the same call as client.invoice(request).
From an AI agent
With pdfops-mcp installed, "Invoice Globex for 3 days of consulting at $650, 8.5% tax, due in 30 days" becomes one pdf_invoice tool call with the same JSON, and the PDF is written to disk rather than passed through the conversation.
The fields
| Field | Type | Notes |
|---|---|---|
from, to | string or { name, lines? } | Required. Up to 6 address lines each. |
items | array, 1 to 100 | Required. Each { description, quantity?, unit_price }; quantity defaults to 1. |
invoice_number | string | Printed in the header and used as the PDF title. |
date, due | string | Printed as given. date also pins the metadata dates. Omit it and no date line is printed. |
currency | ISO 4217 code | Default USD. Formatting only. |
tax_rate | number, 0 to 100 | One percentage applied to the subtotal. |
notes | string, up to 1,000 characters | Printed under the totals. |
When this is the wrong tool
- You need your own design. There is one fixed layout with no logo slot. Build your design once as a fillable PDF and use /api/fill-form, or use an HTML-template renderer such as PDFMonkey.
- You need non-Latin scripts. Characters outside the Western European set print as question marks.
- You need A4, per-line tax, discounts or multiple tax rates. Not supported; the layout is US Letter with one tax rate on the subtotal.
- You need e-invoicing formats (UBL, Factur-X, ZUGFeRD, Peppol). The endpoint returns PDF only.
- You need the PDF hosted. Nothing is stored; you get the bytes and keep them.
Prefer a form to a curl call? The free invoice generator runs the same endpoint in the browser and shows the exact request it sent.
Frequently asked
Is there a free JSON to invoice PDF API?
Yes. POST https://pdfops.dev/api/invoice works with no key and no signup for 100 requests per IP per month, and a free key (no card) gives 250 a month. Free and keyless output carries a small "Generated with pdfops.dev" footer line; Indie ($16/month, 4,000 requests) and Pro ($79/month, 25,000) output has none.
Do I need to design a template first?
No. The endpoint ships its own layout: header with number and dates, FROM and BILL TO blocks, an items table, subtotal, tax, total and notes. You send data only. If you need your own design, build it once as a fillable PDF and send values to /api/fill-form instead.
Is the output really byte-identical for the same JSON?
Yes. The PDF's metadata dates are pinned to the invoice date you send (or a fixed epoch when you omit it), so nothing in the file depends on when it was rendered. The request on this page returns sha256 9c7a206f…e87326 on every call. Webhook retries and cron re-runs produce the same file, so dedup by hash works.
Which currencies and languages are supported?
Any ISO 4217 code for currency; amounts print as the code plus a two-decimal number such as EUR 850.00, with no conversion. Text uses a Latin font, so Western European characters (é, ü, ñ, €) print correctly, while characters outside that set, such as Chinese, Japanese, Arabic or Cyrillic, print as question marks. Labels are in English.
Can I add a logo, change the paper size or produce e-invoice XML?
Not today. There is one US Letter layout with no logo slot, and the endpoint returns PDF only, not UBL, Factur-X or ZUGFeRD XML. For those, use a fill-form template you design yourself, or a dedicated e-invoicing service.
Can an AI agent or an n8n workflow call it?
Yes. The pdfops-mcp server exposes it to agents as the pdf_invoice tool, the n8n community node n8n-nodes-pdfops has an invoice operation, and the Python (pip install pdfops) and TypeScript (pdfops-sdk) clients have an invoice() method.
← PDFops home · /api/invoice reference · Invoice generator · Invoice use case