Hanzo
OpenapiBooks

Scan a receipt or invoice into a proposed voucher

Takes a receipt or invoice as RAW BYTES — a PDF, an image or plain text, uploaded under its own content type, not wrapped in JSON — extracts what the…

POST /v1/books/scan

Addresshttps://api.hanzo.ai/v1/books/scan
MethodPOST
Operationpost_books_scan
AuthAuthorization: Bearer $HANZO_API_KEY

Takes a receipt or invoice as RAW BYTES — a PDF, an image or plain text, uploaded under its own content type, not wrapped in JSON — extracts what the document says, resolves the vendor's expense category, and answers a DRAFT carrying a balanced voucher proposed for it.

NOTHING IS POSTED. That split is the whole design: the model only ever produces a structured reading of the document, the voucher is assembled deterministically in Go from that reading, and the ledger is written only by the separate book call a human confirms. So a misread scan can propose a wrong draft; it cannot move money. Amounts are exact integer cents end to end — the extraction returns cents, never a decimal — so no rounding enters the ledger.

The draft's id is the FILE HASH, and that is what makes booking idempotent: re-scanning the same bytes addresses the same draft rather than queuing a second one. A row is written to the org's document inbox as a side effect, moving it from unsorted to draft. Scoped to the caller's own org from the validated principal and refused without one; sandbox=true targets the sandbox ledger, and filename is recorded for the inbox. An empty or oversized upload is a 400, and a deployment with no scanner model answers 501.

Request

1 field, body application/octet-stream.

FieldInTypeRequiredDescription
(body)bodystring (binary)yes

Response

StatusBodyMeaning
2XXScanDraftSuccess

2XX body — 32 fields.

FieldInTypeAlwaysDescription
balancedbodyboolean
categorybodystring
confidencebodystring
extractedbodyExtracted
extracted.categorybodystringCategory is the expense bucket the SCANNER guessed, as a slug — a hint only.
extracted.currencybodystringCurrency is the ISO code the document is denominated in.
extracted.issuedAtbodystringIssuedAt is the document's OWN date as YYYY-MM-DD — when the bill was issued, which is not when it was uploaded or when it will post.
extracted.lineItemsbodyLineItem[]LineItems are the individual lines read off the document, where it had any.
extracted.lineItems[].amountCentsbodyintegerAmountCents is that line's amount in whole cents.
extracted.lineItems[].descriptionbodystringDescription is the line as it appears on the document.
extracted.merchantbodystringMerchant is the supplier as printed on the document.
extracted.notebodystringNote is anything else worth carrying from the document that has no field of its own.
extracted.taxCentsbodyintegerTaxCents is how much of that total is tax, in cents.
extracted.totalCentsbodyintegerTotalCents is the document total in whole cents, tax INCLUDED.
questionsbodyQuestion[]
questions[].accountbodystringAccount is the chart number the questioned entry posted to, where one applies.
questions[].amountbodystringAmount is the figure that makes the question concrete, already FORMATTED for display with its currency symbol — a string, not cents, and not for arithmetic.
questions[].idbodystringID is the source transaction the question is about, so answering it leads straight back to the entry that raised it.
questions[].kindbodystringKind is what looked wrong: outlier (a charge far above the usual), reversal (a posting undone), roundoff (a balancing plug big enough to be worth explaining),…
questions[].postedAtbodystringPostedAt anchors the question in time — when the entry it concerns posted.
questions[].textbodystringText is the question itself, written for a founder to answer directly.
scanIdbodystring
vendorbodystring
voucherbodyVoucher
voucher.descriptionbodystringDescription is the human line for the event, e.g.
voucher.legsbodyLeg[]Legs are the sides of the posting.
voucher.legs[].accountbodystringAccount is the chart-of-accounts number this side posts to, e.g.
voucher.legs[].creditbodyintegerCredit is the leg's credit in exact cents.
voucher.legs[].debitbodyintegerDebit is the leg's debit in exact cents.
voucher.postingAtbodystringPostingAt is the RFC3339 instant the event posts at — the time every statement window filters on.
voucher.sourceIdbodystringSourceID is the source event's own id within that namespace.
voucher.sourceKindbodystringSourceKind is the idempotency namespace naming what booked this, e.g.

Failure carries the platform error shape — see Errors.

Examples

hanzo books scan create

Books API · All Hanzo APIs · Interactive reference

How is this guide?

On this page