On this page0
Unfamiliar word? See Glossary: words used in these docs. Looking for the tasks in order? Begin at Start Here.
Reference
Included in: Advanced. Which Plan Includes What
Reading Bills (OCR Extraction)
How an uploaded bill is read: the text is extracted on your instance, the invoice fields are found in it, and anything the reader was unsure about is held for a person to confirm.
What It Does
Finance staff and employees upload a bill as a PDF, DOCX, PNG or JPG file of up to 10 MB. Zonitix reads the vendor, the total, the currency, the invoice date and a spending category from it, plus the invoice number, purchase-order number, tax amount, renewal date and seat count when the document shows them. An administrator has nothing to configure for this: it works as soon as Finance is part of your plan. The step-by-step is in Capture and approve a bill.
A Read Is Not A Booking
Read On Your Instance
If something here is not working as you expect, start with When something goes wrong.
The plain explanation is above. This part is for IT and developers: the exact settings, permission names, API addresses and data fields behind it. Closed: select the heading above to open it. While it is closed, your browser’s find-on-page (Ctrl+F or Cmd+F) does not search this part.
Extraction Flow
Uploads go to the document-parser service (Python, FastAPI) on your instance, through the gateway. The endpoint needs manage_finance or submit_invoice; the administrator role bypasses the permission check.
| Method | Path | Purpose | Auth |
|---|---|---|---|
| POST | /api/parser/parse | Multipart upload of one file (field name: file). Validates, extracts the text, extracts the fields, stores the invoice. Returns document_id, displayId, approvalStatus, holdReasons, currency, the extracted text and extracted_fields | manage_finance | submit_invoice |
1. File Validation
- The extension must be one of
pdf,png,jpg,jpeg,docx,xlsxortxt. The upload screens offer PDF, DOCX, PNG and JPG;.xlsis refused. Anything else returns 400. - The first bytes of the file must match its extension (a magic-number check), and a
.txtfile must decode as UTF-8. A mismatch returns 400. - A file over 10 MB returns 400.
- The file’s SHA-256 is compared with every stored upload before any reading. A byte-identical file returns 409 and names the existing invoice.
- DOCX and XLSX files are ZIP archives, so the archive directory is inspected before it is unpacked. More than 5,000 entries, more than 200 MB declared uncompressed, or a compression ratio above 100× is refused.
2. Text Extraction
- PDF: each page’s selectable text is read with PyMuPDF. The page is read a second time without clipping to the page box, and the fuller result is kept, so text a faulty generator placed off the page is not lost. A page with no selectable text (a scan) is rendered to an image and read with Tesseract OCR.
- PNG / JPG: read with Tesseract OCR.
- DOCX: paragraph text. XLSX: the first sheet as text. TXT: the file as is.
3. Field Extraction
Fields are found with labelled patterns and keywords. This is a heuristic extractor, not a trained layout model, and it does not use the position of text on the page.
- Total: the amount beside the strongest total label found (for example “balance due”, “amount due”, “grand total”), ignoring amounts labelled as a previous balance, a subtotal or an amount already paid. With no labelled total, the largest amount in the document is used and the confidence score drops.
- Thousands and decimal separators: settled first from the document’s own unambiguous amounts, and otherwise from your regional setting (Branding → Regional) and the currency. An amount such as “1,234” that these do not settle, or on which they disagree, is flagged as ambiguous instead of trusted.
- Currency: a detected code or symbol. With none, your base currency is assumed and the invoice is marked so a reviewer confirms it.
- Invoice date: the date beside an invoice-date label, otherwise the first readable date. With no date, the upload date is stored as a placeholder and the invoice is held.
- Vendor: a known vendor name near the top of the document, otherwise a support email’s domain, otherwise the first line that is not an invoice heading.
- Also read when present: invoice number, PO number, tax amount, renewal date, seat count and a category.
- Line items: a line is kept only when its quantity × unit price reproduces the line amount (within 1%, or 0.02 if that is larger). A line that does not check out is dropped, so an invoice may be stored with no lines.
The extractor scores its own read from 0 to 1. It starts at 1.0 and loses 0.4 when the total came from the largest-amount fallback (0.6 when no amount was found at all), 0.25 when no vendor was found, 0.1 when no date was found, and 0.4 when the total’s separator is ambiguous.
4. Optional Local-Model Pass
When the score is below 0.8, the text (its first 3,500 characters) is given to the language model that runs on your instance, which is asked for the vendor, total, invoice number and tax. A total is accepted only when the digits of its whole-number part appear in the document text. A tax amount needs the same check, must be below the total, and is taken only when the extractor found no tax. An invoice number is accepted only when it appears in the text and the extractor found none; a vendor name only when the extractor found none (this one is not checked against the text). The score is raised (to at most 0.9) only when the total itself was re-read, so a new vendor, tax or invoice number never releases an invoice from review. A model error leaves the extractor’s result unchanged. The pass is on by default: turn it off with the finance.llm_extraction setting (Finance → Settings), and it does not run when local AI is disabled on the instance (ENABLE_LOCAL_AI).
5. Held For Review, Or Draft
The invoice is stored as NeedsReview when any of these is true, and as Draft otherwise:
- the score is below the review floor (
finance.review_confidence_floor, default 0.80); - the total’s thousands/decimal separator is ambiguous;
- the invoice date is missing, or its day/month order is ambiguous;
- there is no exchange rate for the invoice’s currency;
- another invoice with the same vendor, amount and date is already stored (a likely duplicate).
The response lists the reasons in holdReasons. When the upload would land as a Draft and another invoice with the same vendor and invoice number is already stored and is neither rejected nor held for review, the upload is refused with 409. A held upload is stored regardless; the same check runs again at submit, which returns 409 for a duplicate invoice number. The full extracted text is stored with the invoice, and so is the original file. How a held invoice is released, and everything after that, is on Finance Operations.