API reference
Generate PDFs from JSON using invoice, receipt, and report templates.
Quick start
- Get your API key — Sign in to the dashboard, create an API key, and copy it (shown once).
- Make your first call — Send a
POSTto/api/v1/generatewithX-API-Keyand JSON body (see below). - Get your PDF — The response is raw PDF bytes (
Content-Type: application/pdf). Save to a file (for examplecurl -o out.pdf).
Minimal invoice example (replace YOUR_API_KEY):
curl -X POST "https://pdfapi-nu.vercel.app/api/v1/generate" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"template":"invoice","data":{"companyName":"Acme Design Co.","fromAddress":"100 Market St\nSan Francisco, CA","toAddress":"Jane Buyer\n200 Oak Ave\nAustin, TX","lineItems":[{"description":"Brand kit","quantity":1,"unitPrice":2500},{"description":"Monthly retainer","quantity":3,"unitPrice":800}],"taxRate":0.0825}}' \
-o invoice.pdfAuthentication
Every request to /api/v1/generate must include a valid API key issued from your account. Create and manage keys in the dashboard.
Send the key in the X-API-Key header ( case-insensitive). There is no query-string or Bearer token flow for this endpoint.
Base URL
https://pdfapi-nu.vercel.app
All paths below are appended to this origin (for local development, use your own http://localhost:3000).
POST /api/v1/generate
Renders a PDF from a template discriminator and a data object. Request body must be JSON.
- Success
200— PDF bytes,Content-Disposition: attachment; filename="{template}.pdf"
Template: invoice
"template": "invoice". Tax is expressed as a decimal rate (for example 0.1 for 10%). The server adds line totals, subtotal, tax amount, and grand total for rendering.
data — JSON Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["companyName", "fromAddress", "toAddress", "lineItems", "taxRate"],
"properties": {
"companyName": { "type": "string", "minLength": 1 },
"fromAddress": { "type": "string", "minLength": 1 },
"toAddress": { "type": "string", "minLength": 1 },
"lineItems": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["description", "quantity", "unitPrice"],
"properties": {
"description": { "type": "string", "minLength": 1 },
"quantity": { "type": "number", "exclusiveMinimum": 0 },
"unitPrice": { "type": "number", "minimum": 0 }
},
"additionalProperties": false
}
},
"taxRate": { "type": "number", "minimum": 0 }
},
"additionalProperties": false
}Example request body
{
"template": "invoice",
"data": {
"companyName": "Acme Design Co.",
"fromAddress": "100 Market St\nSan Francisco, CA",
"toAddress": "Jane Buyer\n200 Oak Ave\nAustin, TX",
"lineItems": [
{ "description": "Brand kit", "quantity": 1, "unitPrice": 2500 },
{ "description": "Monthly retainer", "quantity": 3, "unitPrice": 800 }
],
"taxRate": 0.0825
}
}curl
curl -X POST "https://pdfapi-nu.vercel.app/api/v1/generate" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"template":"invoice","data":{"companyName":"Acme Design Co.","fromAddress":"100 Market St\nSan Francisco, CA","toAddress":"Jane Buyer\n200 Oak Ave\nAustin, TX","lineItems":[{"description":"Brand kit","quantity":1,"unitPrice":2500},{"description":"Monthly retainer","quantity":3,"unitPrice":800}],"taxRate":0.0825}}' \
-o invoice.pdfTemplate: receipt
"template": "receipt". paymentMethodLast4 must be exactly four digits.
data — JSON Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["merchantName", "date", "items", "amountPaid", "paymentMethodLast4"],
"properties": {
"merchantName": { "type": "string", "minLength": 1 },
"date": { "type": "string", "minLength": 1 },
"items": {
"type": "array",
"minItems": 1,
"items": { "type": "string", "minLength": 1 }
},
"amountPaid": { "type": "number", "minimum": 0 },
"paymentMethodLast4": { "type": "string", "pattern": "^[0-9]{4}$" }
},
"additionalProperties": false
}Example request body
{
"template": "receipt",
"data": {
"merchantName": "Corner Cafe",
"date": "2026-04-01",
"items": ["Latte", "Blueberry muffin"],
"amountPaid": 12.47,
"paymentMethodLast4": "4242"
}
}curl
curl -X POST "https://pdfapi-nu.vercel.app/api/v1/generate" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"template":"receipt","data":{"merchantName":"Corner Cafe","date":"2026-04-01","items":["Latte","Blueberry muffin"],"amountPaid":12.47,"paymentMethodLast4":"4242"}}' \
-o receipt.pdfTemplate: report
"template": "report". tableData is an array of objects with string keys; each value may be a string, number, boolean, or null.
data — JSON Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["title", "dateRange", "summaryStats", "tableData"],
"properties": {
"title": { "type": "string", "minLength": 1 },
"dateRange": { "type": "string", "minLength": 1 },
"summaryStats": {
"type": "array",
"items": {
"type": "object",
"required": ["label", "value"],
"properties": {
"label": { "type": "string", "minLength": 1 },
"value": { "oneOf": [{ "type": "string" }, { "type": "number" }] }
},
"additionalProperties": false
}
},
"tableData": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": {
"oneOf": [
{ "type": "string" },
{ "type": "number" },
{ "type": "boolean" },
{ "type": "null" }
]
}
}
}
},
"additionalProperties": false
}Example request body
{
"template": "report",
"data": {
"title": "Weekly operations",
"dateRange": "2026-03-25 to 2026-03-31",
"summaryStats": [
{ "label": "Orders", "value": 1284 },
{ "label": "Revenue", "value": "$48,200" }
],
"tableData": [
{ "sku": "A-1", "units": 90, "inStock": true },
{ "sku": "B-4", "units": 12, "inStock": false }
]
}
}curl
curl -X POST "https://pdfapi-nu.vercel.app/api/v1/generate" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"template":"report","data":{"title":"Weekly operations","dateRange":"2026-03-25 to 2026-03-31","summaryStats":[{"label":"Orders","value":1284},{"label":"Revenue","value":"$48,200"}],"tableData":[{"sku":"A-1","units":90,"inStock":true},{"sku":"B-4","units":12,"inStock":false}]}}' \
-o report.pdfGET /api/v1/templates
Returns metadata for templates available on the generate endpoint. No API key required.
Response shape: { "templates": [ { "id", "name", "description" } ] }
curl -s "https://pdfapi-nu.vercel.app/api/v1/templates"Example response JSON:
{
"templates": [
{
"id": "invoice",
"name": "Invoice",
"description": "Itemized invoice with line totals, tax, and grand total."
},
{
"id": "receipt",
"name": "Receipt",
"description": "Merchant receipt with items, amount paid, and card last four digits."
},
{
"id": "report",
"name": "Report",
"description": "Summary stats and a flexible key-value table."
}
]
}Error responses
Errors use JSON with a consistent shape: error.code, error.message, and optional error.details.
400 — Validation error
Malformed JSON or payload does not match the schema for the template.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request payload",
"details": {
"formErrors": [],
"fieldErrors": {
"data": ["Invalid input"]
}
}
}
}401 — Invalid or missing API key
Omitting X-API-Key or sending an unknown key.
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid API key"
}
}429 — Rate limit or quota exceeded
Too many requests per minute (RATE_LIMITED, may include Retry-After header) or monthly PDF cap reached (QUOTA_EXCEEDED).
{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests",
"details": {
"retryAfterSeconds": 42
}
}
}{
"error": {
"code": "QUOTA_EXCEEDED",
"message": "Monthly PDF quota exceeded",
"details": {
"limit": 200,
"used": 200,
"period": "2026-04"
}
}
}500 — Server error
Unexpected failure or render error; see error.code for specifics.
{
"error": {
"code": "INTERNAL_ERROR",
"message": "Something went wrong"
}
}Rate limits
Limits apply per account (aggregated across your API keys). RPM is a per-minute request cap; monthly values are PDF generations per UTC calendar month.
| Tier | Monthly PDFs | RPM |
|---|---|---|
| Free | 200 | 5 |
| Starter | 2,000 | 30 |
| Growth | 10,000 | 60 |
| Scale | 50,000 | 120 |
Code examples
curl
Write PDF to disk with -o.
curl -X POST "https://pdfapi-nu.vercel.app/api/v1/generate" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"template":"invoice","data":{"companyName":"Acme Design Co.","fromAddress":"100 Market St\nSan Francisco, CA","toAddress":"Jane Buyer\n200 Oak Ave\nAustin, TX","lineItems":[{"description":"Brand kit","quantity":1,"unitPrice":2500},{"description":"Monthly retainer","quantity":3,"unitPrice":800}],"taxRate":0.0825}}' \
-o invoice.pdfPython (requests)
import requests
url = "https://pdfapi-nu.vercel.app/api/v1/generate"
headers = {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
}
payload = {
"template": "invoice",
"data": {
"companyName": "Acme",
"fromAddress": "123 St",
"toAddress": "456 Ave",
"lineItems": [
{"description": "Item", "quantity": 1, "unitPrice": 100},
],
"taxRate": 0.1,
},
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
with open("invoice.pdf", "wb") as f:
f.write(r.content)
print("Wrote invoice.pdf")Node.js (fetch)
Requires Node 18+ for global fetch.
const url = "https://pdfapi-nu.vercel.app/api/v1/generate";
const res = await fetch(url, {
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
template: "invoice",
data: {
companyName: "Acme",
fromAddress: "123 St",
toAddress: "456 Ave",
lineItems: [{ description: "Item", quantity: 1, unitPrice: 100 }],
taxRate: 0.1,
},
}),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(`HTTP ${res.status}: ${JSON.stringify(err)}`);
}
const buf = Buffer.from(await res.arrayBuffer());
await import("node:fs/promises").then((fs) => fs.writeFile("invoice.pdf", buf));
console.log("Wrote invoice.pdf");AI agent integration
- Tool design — Expose a single tool such as
generate_pdfwith argumentstemplateanddatamatching the JSON schemas above. Map tool output to a POST body. - Secrets — Store
X-API-Keyin environment variables or your agent runtime secret store; never embed keys in prompts or logs. - Binary responses — The success body is PDF bytes, not JSON. Agents should write to a file, upload to object storage, or base64-encode for downstream tools — not parse as JSON.
- Errors — On non-2xx responses, parse JSON for
error.codeand retry with backoff only for429(respectRetry-Afterwhen present). - Validation — Validate
dataagainst the template schema before calling the API to reduce 400s and cost.