Nothing gets paid
until it’s plumb.
Accounts payable for a real estate group that runs construction, private lending, materials importing and freight across half a dozen LLCs. Invoices arrive by email. Bills land in QuickBooks. The one that is wrong gets caught before the check prints.
Working prototype — 31 screens, real interactions, no sign-in.
Eight stages, and a person only appears in two of them
This is the actual sequence, in order. Six AI prompts do the reading and the coding; the approvers do the deciding. Everything is logged, and nothing posts to your books without a human having seen it.
Intake
Email, scan, or vendor portal. Sender resolves to a vendor, or lands in an unmatched queue that teaches itself.
DKIM checkExtract
Header fields and line items with per-field confidence and bounding boxes.
invoice_ocr_extractMatch
Vendor by EIN, then the subcontract or PO, then the delivery ticket. Two-way and three-way.
EIN · commitmentCode
Job, phase, cost code, cost type, then the GL account. Split across LLCs where it needs to be.
project_infer · gl_codeCheck
Duplicates, over-billing, expired insurance, missing waivers. Anything failing blocks the pay button.
duplicate_detectRoute
Four tiers by amount, entity, method and vendor age. Wires and new vendors always reach the principal.
routing_recommendApprove
Each approver gets a two-line brief on why the invoice looks normal — or does not.
humanPost & pay
Bill into QuickBooks Online or an IIF file for Desktop. Check run, waivers out, Positive Pay file built.
QBO · IIF · ACHEvery module is live in the prototype
Click any of these and you land in the real screen with real data behind it. Nothing below is a picture.
Built for a business that pours concrete, not one that ships software
CSI cost codes with cost types. Retainage held at 10% into its own account. Conditional waivers out with the check and unconditional back before the next draw. Certificates that expire per policy line, not per certificate. A prompt-pay clock that knows New Jersey gives you ten days to pass a payment down to your sub.
The six prompts, with what they actually return
Confidence thresholds are set per field type rather than globally, because a long description naturally scores lower than a six-character invoice number. Each prompt also carries named blockers that stop straight-through processing no matter how confident the model is.
The integrations, described the way your developer would describe them
DocuSign eSignature
- JWT Grant with an RSA keypair and an impersonated user —
signature impersonation. No refresh token; the hour-long access token is re-minted from a fresh assertion. - Envelopes from templates with
templateRoles, and record IDs carried as hidden envelope custom fields so the webhook is a lookup rather than a guess. - Connect webhooks verified against
X-DocuSign-Signature-1— base64 HMAC-SHA256 over the raw body, accepting any of up to 100 keys during rotation. - Recipient
AutoRespondedgets its own state, because a bounced address is a different problem from a declined signature.
QuickBooks Online
- One app, one realm per LLC, tokens stored per realm.
- The refresh token rotates roughly every 24 hours — refreshing with a stale one revokes the whole authorization and forces a browser reconnect. That countdown is on the health screen.
- Bill lines map
AccountRefto the GL,CustomerRefto the job,ClassRefto the cost code andDepartmentRefto the location. - Errors get distinct states:
6240,6140,5010,3200,429.
QuickBooks Desktop
- Tab-delimited IIF with
!TRNS/!SPL/!ENDTRNS. - The TRNS line carries the A/P credit and is negative; the SPL expense lines are positive, so every block nets to zero.
- A preflight validator checks that every vendor, account,
Customer:Joband class already exists before the file downloads. - ASCII only — a UTF-8 BOM breaks the import outright.
Bank & Positive Pay
- Issue file in comma-delimited or fixed-position layout, with configurable amount format and date format.
- Payee normalization preview, because "&" versus "and" is the top cause of a false Payee Match exception.
- Exception decisioning with the bank's own reason codes and a live countdown to cutoff.
- ACH debit filtering off a standing approved list.
Where it runs
A small always-on webhook receiver in the cloud, and the heavy work on a mini-PC in your own office where the QuickBooks Desktop files already live.
What it saves, on your numbers
Move the sliders. The maths uses the published Ardent and APQC benchmarks, and assumes you land mid-pack rather than best-in-class.
Assumes 78% of handling time removed on the straight-through share, duplicates cut by four fifths, and half of available 2/10 discounts captured. Conservative on all three.
The controls a CFO asks about
Segregation of duties
Whoever creates a vendor can never approve its first invoice. Whoever enters an invoice can never release its payment. The vendor master change log is its own report, because it is the first thing an auditor asks for.
Bank-change callback
Any change to a vendor's banking freezes payment until someone calls the number already on file — never one supplied in the request — logs who they reached, and a second person signs off.
Compliance gate
An expired certificate or a below-minimum limit blocks payment mechanically. Not a warning someone can click past.
Immutable audit trail
Every field change, every approval, every model decision, with the confidence and the cost. Exportable as an evidence packet per invoice.
What this prototype is, honestly
- Every screen, every interaction, every filter and sort
- The full data model — entities, projects, vendors, 91 cost codes, 69 invoices, commitments, waivers, envelopes
- The workflow logic: routing tiers, compliance gating, duplicate scoring, retainage
- File formats: the IIF structure, the Positive Pay layouts, the QuickBooks field mapping
- Statutory detail: N.J.S.A. 2A:44A-38 on waivers, the Prompt Payment Act clocks, NJ retainage
- No backend. No Python running, no database, no API calls to anyone
- Vendors, projects and people are invented — no real company data is in here
- Documents are drawn, not scanned. The OCR is scripted, not live
- Nothing sends an email, signs an envelope, or moves a dollar
- Insurance minimums and approval ceilings are sensible defaults, not yours
The whole thing is one folder of HTML. Open index.html
and it runs — no install, no server, no npm. All the data lives in a single
data.js
file, so swapping in your real vendors and jobs is a find-and-replace, not a rebuild.
Start where the work is
Open the review screen and try to approve an invoice you should not. The system will stop you, and it will tell you exactly why.