The AP 3-way match is the oldest AP control in the book. The PO says “we agreed to buy these 100 widgets for $5,000.” The vendor invoice says “we delivered the widgets and want $5,000.” The goods-receipt says “yes, 100 widgets arrived.” Match all three; pay. Disagree on any one; investigate.
The control survived because it works. It also survived because for decades it was painful to operate at scale — every invoice required manual lookup against PO + GR data. AI changes the operational cost; the control stays the same.
This article walks how to automate the 3-way match with AI while preserving the SoD chain that makes it audit-defensible.
The 5-step AP workflow with closegate
1. RECEIVED → invoice arrives (email, EDI, portal)
2. CODED → line items mapped to GL accounts
3. MATCHED → 3-way match runs (PO ↔ invoice ↔ goods-receipt)
4. APPROVED → human approves for payment
5. PAID → payment run submits to bank
Each transition has a tier annotation. The agent does most of the work; humans confirm at the right points.
Step 1: RECEIVED (T0)
The agent ingests the invoice via ap_ingest_invoice (T0 — read-only persist). Vendor adapters handle the heterogeneity: Stripe, Plaid, Mercury for cards/banks; QuickBooks, NetSuite, Codat, Merge for ERPs. New adapters take ~150 LOC.
No human review at this step; the agent’s job is to normalize and persist.
Step 2: CODED (T1 with audit)
ap_propose_invoice_coding (T0) — the LLM proposes GL-account codings for each invoice line. It reads the line description, looks at historical coding for the same vendor, and proposes.
ap_confirm_invoice_coding (T1) — the controller (or the agent, with tier 1 auto-confirm enabled below materiality) confirms the codings. Below-materiality auto-confirm is reasonable here; coding errors are reversible up until the invoice is approved for payment.
Step 3: MATCHED (T0)
ap_match_three_way (T0) — the deterministic matcher runs against PO + invoice + GR. Three outcomes:
- Match on all three within tolerance → flag as MATCHED, ready for approval
- Match on PO + invoice but no GR yet → flag as PENDING_RECEIPT (goods haven’t arrived)
- Tolerance breach on any field → flag as EXCEPTION + assign to AP manager
The matcher is deterministic — no LLM call. The LLM’s role at this step is suggesting why the exception happened (“vendor sent revised price; PO not yet amended”), not making the match decision itself.
Step 4: APPROVED (T2 — HITL required, SoD: actor ≠ PO requestor)
ap_approve_invoice_for_payment (T2) — a human approves the invoice. The gate enforces:
- The approver must be a different actor identity than the PO requestor (SoD)
- The approver must have the appropriate authority level (e.g., not a junior AP clerk on $50K invoices)
- Sensitive-account routing fires if any line touches a sensitive account
- Materiality threshold fires if the total exceeds the team’s threshold
This is the gate’s chokepoint moment. The LLM cannot approve; the runtime denies on source == llm AND actor.kind == llm. The approver is a human with an OIDC token tied to an actor identity.
Step 5: PAID (T3 — dual HITL, requestor ≠ approver ≠ payer)
ap_propose_payment_run (T2) — group APPROVED invoices into a draft payment run.
ap_approve_payment_run (T2) — controller approves the payment run. SoD: approver ≠ requestor.
ap_submit_payment_run (T3) — payment hits the bank/ACH. Dual HITL. Three distinct humans, three distinct actor identities. requestor ≠ approver ≠ payer. No prompt can override. No exception.
The runtime checks at submit time:
- The payer actor must not be the approver
- The payer actor must not be the requestor
- All three actors must be authenticated humans (not engine, not llm)
If any check fails, the gate denies with SOD_SAME_ACTOR and writes a POLICY_VIOLATION event.
What the AI agent is good at
- Invoice coding suggestions. The LLM proposes the GL account for each line based on description + vendor history. Accuracy is typically 85%+ on first attempt; humans correct the rest.
- Duplicate detection. Vendor name normalization across spelling variants (“Acme Corp” vs “Acme Corporation” vs “ACME, Inc.”). The deterministic matcher handles exact duplicates; the LLM catches near-duplicates.
- Exception explanation. When a tolerance breach hits, the LLM proposes a likely reason (“price increase since PO; vendor sent revised quote attached to invoice email”). The human still confirms.
- Vendor bank-change detection. The LLM flags when a new bank account differs from prior payments to the same vendor — a sensitive-account routing always fires here.
What the AI agent is NOT allowed to do
- Approve an invoice for payment. Always T2 minimum; always HITL.
- Submit a payment run. Always T3; always dual HITL.
- Override the materiality threshold. Threshold is policy.yaml; policy.yaml is git; PR review required.
- Override the SoD check. The runtime enforces this regardless of LLM behavior.
The fraud vectors closegate’s AP flow defends against
- Vendor bank-change attack. Email from “vendor” requesting payment to new bank account. The gate routes vendor bank-change events to HITL regardless of materiality. The AP manager sees the change, calls the vendor on a known number, confirms.
- Duplicate invoice submission. Vendor submits the same invoice twice from variant email addresses. The LLM catches this via vendor identity normalization; the gate flags as exception.
- PO splitting. Invoice broken into two below-threshold pieces to avoid approval requirements. The gate aggregates by vendor + week + PO; flags totals over the threshold even if individual invoices are below.
- Phantom vendor. Invoice from a vendor never paid before. The LLM flags vendor-first-payment as exception; HITL routing fires.
- Same-actor approval-and-payment. The dual-HITL T3 chain prevents one actor from both approving the run and releasing to bank.
What this gives your AP team
- 70%+ reduction in manual coding effort (LLM proposes; humans confirm)
- 100% of payments cleared by an SoD chain that survives audit walkthrough
- Tamper-evident audit log with verbatim policy clauses
- Vendor bank-change attack defense by default
- Reproducible PBC bundle via
closegate-engine audit-evidence-export
What to read next
- Segregation of duties for AI agents
- Materiality thresholds for AI
- The 4-tier reversibility model
- The 2:14am narrative — the vendor bank-change attack in detail
- For finance teams