Turn any JSON into a PDF with one API call
POST your JSON object to https://api.quirepdf.dev/v1/render with your API key, and the response body is the PDF. No template is needed; to make it read like a real document, add a title, from and to, set currency, and order the keys the way you want the sections to appear.
curl https://api.quirepdf.dev/v1/render \
-H "Authorization: Bearer $QUIRE_API_KEY" \
-H "Content-Type: application/json" \
-d @data.json \
-o data.pdf
That’s the whole integration. Get a key with npx quirepdf-cli login [email protected]; the free plan includes 20 renders a month, and watermarked test renders (?test=true) are unlimited while you build.
Quire takes any JSON object up to 1 MB. If the data looks like an invoice, receipt, quote, credit note, statement or certificate, it uses that template. Otherwise it uses a generic layout that turns keys into sections, objects into key/value grids and arrays of objects into tables.
Raw JSON works, but reads like a dump
Here’s an export straight from a reporting database:
{
"id": "rep_7Hq2",
"account": { "id": "acct_19", "name": "Little Owl Café" },
"period_start": "2026-09-01T00:00:00Z",
"period_end": "2026-09-30T23:59:59Z",
"stats": { "orders": 1284, "revenue_cents": 2391450, "avg": 18.62 },
"top": [
{ "name": "Flat white", "n": 412, "rev": 185400 },
{ "name": "Croissant", "n": 388, "rev": 112520 }
]
}
It renders, and the PDF is tidy: a “Document” heading, a Details panel with the ID and the two timestamps, an Account section, a Stats grid and a two-column table. But the heading is generic, the timestamps are ISO strings, revenue is in cents, and columns are called “N” and “Rev”. Quire prints what you send. A few minutes of shaping fixes all of it.
Shaped JSON reads like a report
{
"title": "Monthly sales report",
"subtitle": "September 2026",
"number": "REP-2026-09",
"date": "1 Oct 2026",
"currency": "$",
"from": { "name": "Ledgerly", "email": "[email protected]" },
"to": { "name": "Little Owl Café", "address": ["88 Alder Lane", "Portland, OR 97205"] },
"orders": 1284,
"average_order_amount": 18.62,
"summary": "Revenue rose 6% on August, driven by the new breakfast menu. Weekday mornings are now 41% of orders; Sunday afternoons remain the slowest period.",
"top_products": [
{ "product": "Flat white", "units": 412, "sales_amount": 1854.0 },
{ "product": "Croissant", "units": 388, "sales_amount": 1125.2 },
{ "product": "Breakfast burrito", "units": 236, "sales_amount": 2124.0 }
],
"totals": { "gross_total": 23914.5, "refunds": 182.0, "net_total": 23732.5 },
"notes": "Figures exclude gift card sales. Contact [email protected] with questions."
}
The result:
- A letterhead with the Ledgerly mark and email on the left, and “Monthly sales report” with REP-2026-09 on the right, then “September 2026” underneath.
- A To block for Little Owl Café beside a Details panel: Date, Orders
1,284, Average order amount$18.62. - A Summary paragraph, because the string is longer than 80 characters.
- A Top products table with right-aligned Units and Sales amount columns (
$1,854.00). - A Totals grid:
$23,914.50,$182.00,$23,732.50. - A Notes block, and a footer reading “Monthly sales report · REP-2026-09” with “1 of 1”.
What made the difference
| Change | Why |
|---|---|
Added title, subtitle, number, date |
These reserved keys build the header and footer |
Added from and to |
from becomes the letterhead; to becomes an addressed block |
Set currency |
Numbers whose keys contain amount, total, price, cost, fee, tax, refund and similar words print as money |
Renamed rev to sales_amount and sent dollars, not cents |
Only money-like key names get money formatting, and Quire doesn’t convert units |
| Pre-formatted dates | Quire prints strings exactly as given |
| Dropped internal IDs | Every key you send becomes part of the page |
| Ordered keys deliberately | Sections appear in your JSON’s key order |
| Sent the totals | The generic layout never sums a column for you |
The full list of rules, including how nested objects and mixed arrays render, is in Templates and layout.
Check before you render
Validation is free and tells you which layout your data will get:
curl https://api.quirepdf.dev/v1/validate \
-H "Authorization: Bearer $QUIRE_API_KEY" \
-H "Content-Type: application/json" \
-d @report.json
{ "valid": true, "template": "document", "source": "fallback", "hint": null, "errors": [] }
If hint names a template, such as invoice (seller is required), your data is one or two fields away from a purpose-built layout that also computes totals. Add the missing field, or name the template with ?template=invoice to see every problem.
Preview without opening the PDF
curl "https://api.quirepdf.dev/v1/render?format=png" \
-H "Authorization: Bearer $QUIRE_API_KEY" \
-H "Content-Type: application/json" \
-d @report.json -o report.png
format=png returns the first page as an image (add &page=2 for another). It’s handy for thumbnails, emails and visual tests. The x-pages response header tells you how many pages the document has.
From a pipeline or cron job
The CLI wraps the same call and reads stdin, so it fits in a shell pipeline:
export QUIRE_API_KEY=qk_live_...
./export-report.sh | npx quirepdf-cli render - -o "report-$(date +%Y-%m).pdf"
It exits with code 1 and prints every problem if the data is rejected, so a failed render fails the job.
Limits
Requests can be up to 1 MB, render for up to 5 seconds and produce up to 200 pages. Only successful renders count against your monthly quota. See the Render API reference for every parameter and header.