Validate invoice data before you send the invoice
Invoices are one of the few documents where being wrong is expensive twice: once when the customer notices, and again when you reissue and explain. Most of that risk is data quality, and most data quality problems are catchable before the document exists.
The failure you are avoiding
An invoice payload assembled from three systems — a CRM for the buyer, a subscription service for the lines, a tax table for the rates — will eventually produce something like this:
{ "name": "Consulting", "quantity": "-1", "unit_price": "150.00" }
A negative quantity is not a rendering problem. It is a credit note someone built by hand, or an off-by-one in a proration, and it will produce a confidently-formatted PDF with a negative total if nothing stops it.
Validate as a separate step
The point of a standalone validation call is that it is cheap enough to run everywhere:
curl -X POST https://invoicepdfs.com/api/v1/documents/validate \
-H "Authorization: Bearer inv_..." \
-H "Content-Type: application/json" \
-d '{
"document_type": "invoice",
"data": {
"invoice_number": "INV-2026-0001",
"issue_date": "2026-08-30",
"currency": "USD",
"seller": { "name": "Acme Inc." },
"buyer": { "name": "Northstar LLC" },
"line_items": [
{ "name": "Consulting", "quantity": "1", "unit_price": "150.00" }
]
}
}'
{ "data": { "valid": true } }
No document is produced and no render is consumed, so you can call it on every save, in a nightly sweep over drafts, and in CI against your fixtures.
Read the error, not just the status
A validation response is only useful if it names the field:
{
"error": {
"status": 422,
"code": "unprocessable_entity",
"message": "line_items[0].quantity must be > 0"
}
}
line_items[0] tells you which line. That is the difference between a log entry
someone acts on and one they mute.
Worth stating plainly: a validation endpoint that returns 500 on invalid input
is not a validation endpoint. The caller cannot distinguish bad data from an
outage, so the sensible client behaviour — retry — is exactly wrong, and the
payload will never succeed however many times it is sent.
Where to put the call
On write. When a draft is saved, validate it. The person who entered the data is still present, which is the cheapest possible moment to fix it.
Before finalizing. Finalizing is the point of no return — a finalized invoice should not be edited, only credited. Validate immediately before.
In CI. Keep a handful of representative payloads as fixtures and validate them on every build. This is what catches a schema change in an upstream system before it reaches production.
Check the arithmetic separately
Validation answers “is this well-formed”. It does not answer “is this the right amount”. For that, compute the totals without rendering:
POST https://invoicepdfs.com/api/v1/documents/calculate
{ "data": { "calculation": {
"subtotal": { "amount": "200.00", "currency": "USD" },
"tax_total": { "amount": "40.00", "currency": "USD" },
"total": { "amount": "240.00", "currency": "USD" }
}}}
Useful for showing a total in your own UI before the invoice is issued, and for asserting in tests that a change to your pricing logic did not move a number you did not intend to move.
Both endpoints are in the API reference, and neither consumes your render allowance. The Invoice PDF API is the one that produces the document, once the data is known to be good.