Invoice-to-PO Matcher
An agent that reads vendor invoices, checks every line against the purchase order, and holds anything that doesn't match until a person approves it.
- Agent Lab · sample data for Meridian Facilities Co.
- Design & Engineering
- Agent Lab · 2026
Overview
Accounts payable teams still key invoices in by hand and catch overbilling by luck. This agent reads each vendor invoice PDF, extracts it into a strict schema, looks up the purchase order in the accounting system, and runs deterministic checks on vendor, quantities, unit prices, arithmetic, sales tax and duplicates. Clean invoices wait for one-click approval; problem invoices are held with a plain-English explanation and a suggested action. Nothing reaches accounting without a person's decision, and every decision is logged.
The Problem
Three-way matching is slow, repetitive and easy to get wrong. Vendors bill a few percent above the agreed price, charge for more units than were ordered, apply the wrong tax rate, or send the same invoice twice. Letting a language model decide what gets paid is not acceptable either: approvals over money need to be explainable and reproducible.
- ▸Every vendor formats invoices differently: "PO Number", "Customer PO", "Your order ref".
- ▸Small overcharges add up and rarely get caught during manual entry.
- ▸Finance needs to know exactly why something was paid or held, every time.
The Solution
The model does what models are good at, reading messy documents, and code does what code is good at, deciding. Extraction is validated against a zod schema and repaired automatically when it fails. The match decision is a pure function with unit tests, so the same invoice always gets the same answer.
- ✓Model extracts vendor, PO reference, dates, line items, subtotal, tax and total into a validated schema.
- ✓Code checks the PO exists, the vendor matches, quantities don't exceed the order, prices are within 2%, tax is recomputed from the PO's rate, and the invoice isn't a duplicate.
- ✓Only held invoices go back to the model, for a two-sentence explanation and a suggested action.
- ✓A person approves or rejects each invoice; posting the same invoice twice is refused.
Architecture
A pipeline on a shared agent runtime: provider fallback, validated structured output, traced tool calls, and a streaming UI that shows every step.
Extraction
PDF text via unpdf, then structured output against a zod schema. Invalid JSON is sent back with the validation errors for up to two repairs.
Accounting adapter
Purchase-order lookup, duplicate search and bill posting behind one interface. A mock stands in for QuickBooks or Xero; a real adapter implements the same three methods.
Deterministic matcher
A pure function that returns each check with a pass/fail and a reason, plus the dollar amount billed above what the PO allows.
Runtime & UI
OpenRouter, OpenAI and Gemini behind one fallback chain with backoff. Every model call and lookup is streamed to a Next.js page over Server-Sent Events.
Key Features
Checks you can audit
Each invoice shows the checks run in code, which passed, which failed, and why, next to the original document.
Plain-English holds
Held invoices come with a short explanation and a suggested action, such as asking the vendor for a $60 credit note.
Human approval and audit log
Approve and post, post anyway, or reject. Every decision is recorded, and duplicate posting is blocked.
Live run trace
Every model call, lookup, token count and retry appears as it happens, so reviewers can see exactly what the agent did.
Technology Stack
Design & Engineering
Designed and built the agent, the shared runtime it runs on, the sample data and the demo interface.
- ▪Built the provider fallback chain, tool loop and validated structured output in the shared core.
- ▪Wrote the matching rules as a tested pure function and kept the model out of the approval decision.
- ▪Generated seven realistic vendor invoices covering the mismatches AP teams see every week.
- ▪Designed the review screen and the live trace so the business outcome is clear in seconds.
Product UI




Technical Challenges
Keeping the model out of the money decision
Challenge: An LLM that decides what gets paid can't be audited and may answer differently on a second run.
Solution: The model only extracts and explains. Approve or hold is a pure function over the extracted data and the PO, covered by unit tests for every scenario.
Reliable extraction from varied layouts
Challenge: Each vendor labels fields differently and models occasionally return numbers as strings or skip fields.
Solution: A strict schema with field descriptions, provider JSON-schema mode where supported, and automatic repair that feeds validation errors back to the model.
Model outages during a batch
Challenge: Providers return 503s and time out under load, which would stall a batch halfway through.
Solution: Per-request timeouts, exponential backoff with jitter, and fallback to the next model in the chain, all visible in the trace.
Outcomes & Highlights
On the sample batch the agent posts nothing on its own, clears the two clean invoices for approval, and holds the other five for exactly the right reason each time.
- ✓Catches price creep, over-billed quantities, wrong tax, an unknown PO and a duplicate invoice.
- ✓$208 of overbilling flagged across seven invoices, with a suggested action for each.
- ✓End-to-end test runs the full batch offline against a scripted model.
- ✓About 15 seconds for the whole batch on Gemini Flash-Lite.
- 7/7
- Invoices decided correctly
- $208
- Overbilling caught
- 0
- Payments without a person
2 cleared, 5 held for the right reason
On the sample batch
Every post needs approval
