An AI agent can make an invoice, quote, estimate, receipt or payment voucher as a finished PDF with one HTTP request. The PDF is identical to the one the Download PDF button produces. No account, no key, nothing stored.
POST https://invoice.flowsxr.com/api/pdf with the document as JSON.
You get application/pdf back. Send GET to the same URL for a machine-readable version of this page.
curl -sS https://invoice.flowsxr.com/api/pdf \
-H 'Content-Type: application/json' \
-o invoice.pdf \
-d '{
"number": "INV-0042",
"from": { "name": "Studio Nova Ltd", "email": "billing@studionova.com" },
"to": { "name": "Acme Corp", "email": "ap@acme.com" },
"items": [ { "description": "Prototype development", "qty": 24, "rate": 120 } ],
"currency": "NZD", "taxRate": 15, "taxLabel": "GST"
}'
import requests
doc = {"number": "INV-0042", "from": {"name": "Studio Nova Ltd"}, "to": {"name": "Acme Corp"},
"items": [{"description": "Prototype development", "qty": 24, "rate": 120}], "currency": "NZD"}
r = requests.post("https://invoice.flowsxr.com/api/pdf", json=doc, timeout=60)
if r.status_code == 400:
print(r.json()["details"]) # [{"field": "...", "message": "..."}]: fix and resend
r.raise_for_status()
open("INV-0042.pdf", "wb").write(r.content)
const r = await fetch('https://invoice.flowsxr.com/api/pdf', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(doc),
});
if (!r.ok) throw new Error(JSON.stringify(await r.json()));
const pdf = new Uint8Array(await r.arrayBuffer());
docType | Use it when | Notes |
|---|---|---|
invoice | Asking a client to pay for work or goods | Default. Due date defaults to 14 days after the issue date. |
quote / estimate | Proposing a price before work starts | dueDate is printed as "Valid until". |
receipt | Confirming a payment you received | dueDate is printed as "Paid on"; the PDF shows PAID. |
voucher | Recording money you paid out (a contractor, staff, a supplier) | "Pay to" replaces "Bill to"; adds Approved by / Received by signature lines; no due date; no default note. |
Only number, from.name, to.name and at least one item are required. Everything else has a sensible default.
Unknown fields are ignored and reported back as warnings.
| Field | Type | Default | Meaning |
|---|---|---|---|
docType | text | invoice | invoice, quote, estimate, receipt or voucher |
number | text, required | Your document number, e.g. INV-0042, PV-2026-014. PromptInvoice does not number API documents for you. | |
issueDate | YYYY-MM-DD | today (UTC) | Issue date; for vouchers, the voucher date |
dueDate | YYYY-MM-DD | issue + 14 days | Due, valid-until or paid-on date (ignored for vouchers) |
from | object | Your business: name (required), email, phone, address (use \n for new lines), taxId, logo, signature | |
to | object | The client or payee: name (required), email, address, reference (PO or project ref) | |
items | list | 1 to 200 lines of { "description", "qty", "rate" }. rate is the unit price; qty defaults to 1 and may be hours (16.48). | |
currency | ISO 4217 | USD | 150+ codes, e.g. USD, SGD, NZD, EUR, GBP, INR, LKR. Sets the symbol and formatting. |
taxRate | number 0 to 100 | 0 | Percent applied after discount |
taxLabel | text | Tax | GST, VAT, Sales tax |
discountType | percent or fixed | percent | How discountValue is read |
discountValue | number | 0 | Percent (0 to 100) or an amount; capped at the subtotal |
shipping | number | 0 | Added after tax |
customFields | list | none | Up to 10 { "label", "value" } rows shown in the header, e.g. Project, PO number |
paymentDetails | text | empty | Bank details or how to pay |
paymentMethod | text | empty | Vouchers: Bank transfer, Cash, PayNow, Cheque |
approvedBy | text | empty | Vouchers: name printed under the Approved by signature |
notes | text | a thank-you line (none for vouchers) | Footer text; send "" for none |
theme | #rrggbb | #166534 | Accent colour |
layout | stacked or side | stacked | side places From and To side by side |
Never send totals. Subtotal, discount, tax and total are always calculated from the items, exactly as the app does,
so the PDF always adds up. A total field is ignored with a warning.
{
"docType": "voucher",
"number": "PV-2026-014",
"issueDate": "2026-10-02",
"from": { "name": "Studio Nova Ltd", "email": "billing@studionova.com", "address": "12 Example Street\nAuckland 1010" },
"to": { "name": "Jane Contractor", "email": "jane@example.com" },
"items": [
{ "description": "Design services, September 2026", "qty": 1, "rate": 350 },
{ "description": "Advance for October 2026 (deducted from the October payment)", "qty": 1, "rate": 200 }
],
"currency": "SGD",
"paymentMethod": "Bank transfer",
"approvedBy": "Alex Owner"
}
{
"number": "INV-0042",
"issueDate": "2026-10-02",
"dueDate": "2026-10-16",
"from": { "name": "Studio Nova Ltd", "taxId": "123-456-789", "address": "12 Example Street\nAuckland 1010" },
"to": { "name": "Acme Corp", "email": "ap@acme.com", "reference": "PO-2026-014" },
"items": [
{ "description": "Mixed-reality prototype: Unity development", "qty": 24, "rate": 120 },
{ "description": "On-site user testing session", "qty": 2, "rate": 450 }
],
"currency": "NZD", "taxRate": 15, "taxLabel": "GST",
"discountType": "percent", "discountValue": 5,
"customFields": [ { "label": "Project", "value": "September prototype" } ],
"paymentDetails": "Bank: Example Bank\nAccount: 01-0000-0000000-00\nReference: INV-0042"
}
Send images inline as base64 data URIs (PNG, JPEG, WebP or SVG, up to 1 MB each). Links to images are not fetched. The signature is printed above "Approved by" on vouchers and as the authorised signature on other documents.
import base64
logo = "data:image/png;base64," + base64.b64encode(open("logo.png", "rb").read()).decode()
doc["from"]["logo"] = logo
| Status | Body | What to do |
|---|---|---|
200 | The PDF. Content-Disposition carries a filename like INV-0042 - Acme Corp.pdf | Save it. If X-PromptInvoice-Warnings is present, it lists ignored fields. |
400 | { "error", "details": [{ "field", "message" }], "warnings" } | Every problem is listed at once. Fix those fields and resend. |
413 | { "error" } | Body over 4 MB: shrink the logo or signature. |
500 | { "error", "message" } | Rendering failed. Retry once after a few seconds. |
{
"error": "The document has problems. Fix the fields listed and send it again.",
"details": [
{ "field": "number", "message": "is required" },
{ "field": "items[0].rate", "message": "must be a number (the unit price)" }
]
}
The first request after a quiet period can take a few seconds while the renderer starts. Later ones take about a second.
Content-Disposition suggests, and say where you saved it. Sending it to anyone is the person's decision.GET /api/pdf. Short pointer for LLM crawlers: /llms.txt.