PDF Invoices to Review Queue: From Extracted Fields to a Flagged Exception and a Human Decision
If vendor invoices reach you as PDFs, the useful question is how a single odd one reaches a person. This page follows six synthetic invoices from extracted text to fields, five ordered checks, a queue record and a recorded decision, and states what the demonstration does not do.
To turn PDF invoices into a review queue, extract the text, pull out labelled fields, then run fixed checks on every invoice: missing fields, arithmetic, PO match, duplicate and over-PO tolerance. Each invoice that trips a check becomes a queue record, and a person approves, rejects or holds it.
On this page
At a glance
- A PDF invoice becomes a review-queue record in three moves: extract its text, read labelled fields, and run checks that never stop each other.
- Five checks run on every invoice: missing field, arithmetic, PO match, duplicate and over-PO tolerance.
- A check that lacks its input says so: it is listed as not evaluated rather than passed.
- A queue record carries empty decision fields until a person approves, rejects or holds it, and an approval must acknowledge every flag.
- The demonstration starts from extracted text and is synthetic; it shows the method, not an integration or real-volume performance.
From PDF to text to fields
A text-based PDF holds a text layer, and a text extractor reads it. One open-source library's tool, for example, "will extract all text from the given PDF document."[1] A scanned invoice has no text layer until OCR adds one: one OCR tool's documentation says it "adds text “layers” to images in PDFs, making scanned image PDFs searchable."[2]
From the text, the module reads labelled lines: the vendor is the first non-empty line, then Invoice No, Invoice Date, PO Number, Subtotal, Tax and Total Due. A label with no value, or a date not in YYYY-MM-DD form, comes back empty. Money is held in integer cents, and names and numbers are compared after removing case, spaces and punctuation.
- Read the text
Start from each invoice's extracted text.
- Pull the fields
Read vendor, invoice number, date, PO, subtotal, tax and total.
- Run five checks
Run every check on every invoice and record what could not run.
- Queue and decide
Queue each flagged invoice; a person records a decision.
| Line | Extracted text |
|---|---|
| 1 | COBALT FREIGHT |
| 2 | Invoice No: INV-2044 |
| 3 | Invoice Date: 2026-03-06 |
| 4 | PO Number: PO-7004 |
| 5 | Subtotal: 4,310.00 |
| 6 | Tax: 0.00 |
| 7 | Total Due: 4,310.00 |
Illustrative example (synthetic data, not client data or real invoices): the same step on all six invoices gives these fields. Date, subtotal and tax follow in a second table.
| File | Vendor | Invoice no. | PO | Total |
|---|---|---|---|---|
| invoice-01 | ALDER PAPER CO | INV-2041 | PO-7001 | 1250.00 |
| invoice-02 | BIRCH TOOLING | INV-2042 | PO-7003 | 3840.00 |
| invoice-03 | CEDAR LAB SUPPLY | INV 3310 | PO-7003 | 612.50 |
| invoice-04 | COBALT FREIGHT | INV-2044 | PO-7004 | 4310.00 |
| invoice-05 | DELTA JANITORIAL | INV-2045 | (none) | 1944.00 |
| invoice-06 | ELM LABEL WORKS | INV-2046 | PO-7005 | 1010.00 |
| File | Date | Subtotal | Tax |
|---|---|---|---|
| invoice-01 | 2026-03-04 | 1150.00 | 100.00 |
| invoice-02 | 2026-03-05 | 3200.00 | 280.00 |
| invoice-03 | 2026-03-06 | 575.00 | 37.50 |
| invoice-04 | 2026-03-06 | 4310.00 | 0.00 |
| invoice-05 | (none) | 1800.00 | 144.00 |
| invoice-06 | 2026-03-09 | 950.00 | 60.00 |
Two small registers sit beside the invoices. The PO register lists PO-7001 Alder Paper Co 1250.00, PO-7002 Birch Tooling 3480.00, PO-7003 Cedar Lab Supply 980.00, PO-7004 Cobalt Freight 4000.00, PO-7005 Elm Label Works 1000.00 and PO-7006 Delta Janitorial 1944.00, each an open amount. The processed list holds Cedar Lab Supply INV-3310 (2026-02-27) and Alder Paper Co INV-1987 (2026-02-20). Both are synthetic.
The five checks, in order
Every invoice goes through all five checks, so a flag never hides another one. Each check gives a flag, a pass, or a statement that it could not be evaluated. For ranking the resulting queue, see how to prioritize an invoice exception queue; this page stops at producing it.
| Check | Flags when | Not evaluated when |
|---|---|---|
| R1 missing field | Vendor, invoice number, invoice date, PO number or total is absent | Always runs |
| R2 arithmetic | Subtotal + tax does not equal the total | Subtotal, tax or total is missing |
| R3 PO check | The PO does not exist or belongs to another vendor | No PO number on the invoice |
| R4 duplicate | Same vendor and normalized invoice number already processed, or earlier in the batch | Vendor or invoice number is missing |
| R5 over PO tolerance | Total is above the PO open amount by more than the tolerance (Illustrative example: 2.00%) | R3 fails, or no PO number |
R5 needs a trustworthy PO to compare against. If the PO belongs to someone else, comparing the total with its amount would produce a meaningless percentage, so the module records R5 as not evaluated instead of guessing. The same logic applies to every "not evaluated" cell. The related distinction between reading a field correctly and getting the money right is covered in invoice extraction accuracy versus financial accuracy.
The result on six synthetic invoices
Illustrative example (synthetic data): with the default 2.00% tolerance, 2 invoices pass and 4 are flagged. Those counts describe this fixture only and are not a typical rate.
| File | Flags | Not evaluated |
|---|---|---|
| invoice-01 | none | none |
| invoice-02 | R2: subtotal 3200.00 + tax 280.00 = 3480.00, but total is 3840.00; R3: PO-7003 belongs to Cedar Lab Supply, not BIRCH TOOLING | R5 (PO belongs to another vendor) |
| invoice-03 | R4: INV-3310 from Cedar Lab Supply was processed on 2026-02-27 | none |
| invoice-04 | R5: total 4310.00 is 310.00 over the open amount 4000.00 (7.75%; tolerance 2.00%) | none |
| invoice-05 | R1: missing: invoice_date, po_number | R3 (no PO number), R5 (no PO to compare against) |
| invoice-06 | none (1.00% over the PO open amount, inside the 2.00% tolerance) | none |
invoice-03 shows the normalization at work: INV 3310 and INV-3310 become the same key, so the duplicate is found despite the different spelling. invoice-05 shows the opposite risk. With no PO number and no date it trips R1, and two other checks simply cannot run, which is itself a reason for a person to look.
The review-queue record
Each invoice with at least one flag gets one queue record, numbered in file order. Illustrative example (synthetic data): four records result.
| Queue ID | File | Invoice no. | Total | Flags |
|---|---|---|---|---|
| Q-001 | invoice-02 | INV-2042 | 3840.00 | 2 |
| Q-002 | invoice-03 | INV 3310 | 612.50 | 1 |
| Q-003 | invoice-04 | INV-2044 | 4310.00 | 1 |
| Q-004 | invoice-05 | INV-2045 | 1944.00 | 1 |
Every record has the status needs_review. Each also has five decision fields: decision, decided_by, decided_at, acknowledged and note. They are empty until a person fills them. Software raises the flag; it does not decide it.
The human decision
The module enforces a few rules. The decision is approve, reject or hold. decided_by, decided_at (YYYY-MM-DD) and a note are required. An approval must acknowledge every flag on the record. A queue record with no decision stays on hold. In the synthetic fixture, two reviewers, reviewer-1 and reviewer-2, record these decisions, all dated 2026-03-10.
| Queue ID | Decision | By | Acknowledged | Note |
|---|---|---|---|---|
| Q-001 | hold | reviewer-1 | none | Ask the vendor to reissue: the cited PO belongs to another supplier and the total does not add up. |
| Q-002 | reject | reviewer-1 | none | Same vendor and invoice number as an invoice already processed on 2026-02-27; no new delivery. |
| Q-003 | approve | reviewer-2 | R5_over_po_tolerance | Requester confirmed a surcharge; PO amendment requested before payment. |
| Q-004 | hold | reviewer-2 | none | Ask the vendor for the PO number and the invoice date. |
Final dispositions follow. invoice-01 and invoice-06 are ready_for_approval: they pass the checks and continue to the normal approval, and the demonstration pays and posts nothing. invoice-02 is on_hold, invoice-03 rejected, invoice-04 approved and invoice-05 on_hold. That makes 1 approved, 1 rejected and 2 on hold in this fixture only.
The record is the point: who decided, when, why, and which flags they acknowledged. The approval of Q-003 cannot be saved without acknowledging the over-tolerance flag. For the wider pattern of keeping a person in charge of consequential steps, see approval gates for finance workflows and document review with human approval.
Tolerances: what changing the PO tolerance does to the queue
A tolerance is the variance a match may show before it counts as a mismatch. Microsoft's documentation for one ERP product states that "The legal entity default price tolerance is set to zero percent for two-way and three-way matching."[3] It also says price tolerance percentages can be set "for your legal entity, items, and vendors."[3] A university research foundation's guideline describes a breach: a system hold is placed on the invoice, and "The hold prevents payment of the invoice until the problem is resolved or the hold is released manually."[4]
The demonstration's R5 is a single parameter. Illustrative example (synthetic data): at the default 2.00%, the queue holds INV-2042, INV 3310, INV-2044 and INV-2045. The experiment below changes only that parameter.
| Tolerance | Queue | What changes |
|---|---|---|
| 2.00% (default) | INV-2042, INV 3310, INV-2044, INV-2045 | Baseline; INV-2046 at 1.00% over passes. |
| 10.00% | INV-2042, INV 3310, INV-2045 | INV-2044 leaves the queue: its 7.75% overrun is now tolerated. |
| 0.00% | INV-2042, INV 3310, INV-2044, INV-2045, INV-2046 | INV-2046 joins the queue: its 1.00% overrun is now a flag. |
A looser tolerance shrinks the queue by accepting more overruns without a person seeing them; a tighter one grows it. The right setting is a business decision, and it is not something this demonstration can pick for you.
Limitations and when not to use this approach
This demonstration shows how an exception is flagged and decided, not how it performs on your invoices.
- It starts from extracted text: no PDF parsing, no OCR, and nothing here measures extraction quality.
- All data is synthetic and small. Nothing was tested on real invoices or volumes.
- It is a method, not a connector to any AP, ERP or accounting product.
- The checks cover five things only. They do not see a wrong price on a correct total, a fictitious vendor or a mis-coded expense.
- Field reading relies on labels. An invoice whose layout does not use these labels needs a different extraction step first.
- Do not use it when a person already clears the invoice volume without a backlog.
- Do not use it without a PO register or a processed-invoice list to compare against.
- Do not use it when the team has not agreed which flags matter and who may approve.
- Do not use it to pay or post anything: it stops at a decision.
To see how this fits an insurance-invoice workflow, read insurance invoice analysis. Too many flags that turn out to be harmless is a separate problem, covered in false positives in invoice review.
Frequently asked questions
How do you turn PDF invoices into a review queue of exceptions?
Extract the text from each PDF, read labelled fields, then run every check on every invoice. Each invoice with a flag becomes a queue record with empty decision fields, and a person approves, rejects or holds it. A scanned invoice needs OCR first.
What fields do you extract from a PDF invoice?
The demonstration reads vendor, invoice number, invoice date, PO number, subtotal, tax and total. Those are the fields its five checks need. A missing vendor, invoice number, date, PO number or total is itself flagged for a person to look at.
Which checks should run on an extracted invoice?
This demonstration runs five on every invoice: missing field, arithmetic (subtotal plus tax equals total), PO check, duplicate, and over-PO tolerance. A flag never stops the others. Which checks your organization needs is for its accounting and audit leads to decide.
How does a PO mismatch or a duplicate invoice get flagged?
A PO mismatch is flagged when the PO is missing from the register or belongs to another vendor. A duplicate is flagged when the same vendor and normalized invoice number was already processed, or appears earlier in the same batch of invoices.
What does the human reviewer decide and what is recorded?
The reviewer chooses approve, reject or hold. The record keeps the decision, who decided, the date, the flags acknowledged and a note. An approval must acknowledge every flag on the record; with no decision at all, the record stays on hold.
When should you not use this approach?
Skip it when manual review already keeps up, when you have no PO register or processed list to compare against, when the team has not agreed which flags matter, or when invoices have no usable text. It never pays or posts anything.
Related reading
- Insurance invoice analysis is the wider workflow this pattern belongs to.
- Invoice exception queue prioritization covers ranking the queue.
- Extraction accuracy versus financial accuracy explains why a correct read is not a correct payment.
Sources
- The Apache Software Foundation, Command line tools, Apache PDFBox 3.0 (pdfbox.apache.org) (accessed 2026-10-30)
- OCRmyPDF project, Introduction (ocrmypdf.readthedocs.io) (accessed 2026-10-30)
- Microsoft Learn, Set up Accounts payable invoice matching validation, Dynamics 365 Finance (learn.microsoft.com) (accessed 2026-10-30)
- The Research Foundation for SUNY, Tolerances: Purchasing guideline (rfsuny.org) (accessed 2026-10-30)
See where AI could take the most manual work off your plate.
A 30-minute strategy call. We'll map your highest-value automation candidates and what a production system would return.