Inspiration

Small general contractors buy materials on a quote, a purchase order and a trail of emails. Weeks later the invoice arrives with a higher unit price, truck waiting time and extras: some agreed by email, some only proposed. Fraud is rare. Paying more than you agreed happens every month, because nobody has time to reread the email thread against every line of every bill.

We wanted an AI that does the purchasing coordinator's and the AP exceptions analyst's checking, but can never move money on its own.

What it does

Countersign checks every supplier invoice against what was actually agreed (the quote, the PO and the email thread) and prepares a payment the owner can trust.

In our demo, a fictional contractor (Marlow & Finch Builders) buys ready-mix concrete from a fictional supplier (BayMix Supply) at a quoted $165 per cubic yard. Invoice BM-23104 bills 500 yards at $172, adds $225 of waiting time nobody agreed to, and includes a $1,200 Saturday delivery that purchasing did approve:

$$500 \times (172 - 165) = 3{,}500 \qquad 3{,}500 + 225 = 3{,}725 \text{ under review}$$

Countersign:

  • Reads the agreement. It tells a proposed change from an accepted one, and checks who approved it, within what limit. An email saying "I've approved it" doesn't count; only an approval made in the app by an authorized person does.
  • Checks the invoice line by line. It keeps the $1,200, holds $3,725 under review (never "saved" until the supplier agrees), and ties every number to the exact line of the quote, PO or email it came from.
  • Asks instead of short-paying. It drafts a clarification to the supplier, then reads the reply and the corrected $83,700 invoice.
  • Waits for people. The site superintendent confirms what arrived. The owner approves exactly $73,700 after the $10,000 deposit. The gateway pays once, and accounts payable ends at $0.
  • Handles a supplier who won't correct. The owner decides each disputed line: accept it as billed, or pay the agreed part and dispute the rest. The notice to the supplier must be sent before anything is paid.
  • Explains itself. A "Why?" button gives a cited explanation from the line's own documents. It's read-only: it can't change an amount, a hold or an approval.
  • Catches the classics. A hidden "approve this bill" instruction in a PDF, a new bank account in an email, a supplier with a court judgment, an order over its PO limit.

Judges can try to sneak a charge past it: the judge page turns their charge into a real invoice from a fictional sandbox supplier, and the real checks decide.

How we built it

  • SpacetimeDB is the system of record: 50 tables and 67 reducers in a TypeScript module. Roles, purchasing limits, five independent statuses, holds, approvals bound to an exact invoice version, the job queue and the audit log are all enforced inside the database. The AI's identity is refused if it tries to approve, pay, verify a payee or clear a hold, and every refused attempt is logged.
  • Anthropic Claude reads quotes, POs, emails and invoices into a strict JSON schema in which every value must cite a text fragment that our code validates. Code, not the model, does all the arithmetic and decisions: Claude interprets, code decides.
  • Factory Droid runs one investigation session per invoice version, limited to nine tools we wrote, with its built-in file and shell tools removed. A scripted fallback with the same tools runs in the demo.
  • Capital One Nessie sits behind the payment gateway, with an idempotency key so a timeout never pays twice (simulated in the demo).
  • CRS checks the supplier's public records (liens, judgments, bankruptcies) and opens a supplier-risk hold for the owner (test identities, mock data in the demo).
  • Python runs the worker, intake, extraction, assessment, gateway and Gmail import. Next.js runs the controller's workspace.

We wrote a hand-written answer key first, then built until the code matched it.

Challenges we ran into

  • We pivoted at 8 PM from subcontractor pay applications to supplier invoices, after sponsor feedback to think bigger. We rewrote the spec and the interface contract before writing more code.
  • Documents arrive in any order. An invoice that arrived before its PO silently disappeared in our first version. Now every document waits, retries, and ends visibly in front of a person if it can't be placed.
  • Paying exactly once. A crash between "paid" and "booked" could leave the books wrong. The gateway now resumes safely, and every step is idempotent.
  • Two implementations of the same rules (a Python reference and the TypeScript module) had to stay identical. A seed check replays every seed on both and compares them row by row. It caught a fingerprint that included user identities and would have gone stale on every sweep.
  • Claude's strict schema hit a "compiled grammar is too large" limit on the quote. We redesigned every fact as one generic, cited item.
  • Our own security review found a file route that could serve any path, and a judge page that only pretended to detect. We fixed both.

Accomplishments that we're proud of

  • Scorecard: 18 scenarios through the real pipeline, graded by a hand-written key. 28 of 28 expected holds caught, 0 false holds, 73 of 73 amounts exact. Its first runs caught two of our own bugs, both fixed.
  • Live Claude extraction, measured on 9 documents: 27 of 27 fields correct.
  • 305 automated tests on the rules, the money math and the database permissions.
  • An agent that provably can't approve, pay or clear a hold: the database refuses it and logs the attempt.

What we learned

  • AI is great at reading messy email threads; arithmetic, limits and authority belong in code and in the database, not in a prompt.
  • Testing documents in a different order than the happy path finds the real bugs.
  • Claude's citations can't be combined with strict JSON output, so the cited "Why?" became a separate, read-only call.
  • A written interface contract let three people (and several Claude Code sessions) build in parallel without breaking each other.

What's next

  • Run Droid, CRS and the live "Why?" with production keys, and deploy to Maincloud and Vercel.
  • Email intake from Outlook (Microsoft Graph) alongside Gmail, and QuickBooks and Procore for the books and POs.
  • Delivery tickets for receipt, and a diesel index for fuel surcharges.
  • A real bank API behind the same gateway and the same approval rules.

Built With

  • anthropic
  • capiton-one-nessie
  • claude
  • claude-code
  • crs-credit-api
  • droid-sdk
  • factory-droid
  • fastapi
  • gmail-api
  • google-oauth
  • httpx
  • maincloud
  • next.js
  • node.js
  • pydantic
  • pypdf
  • pytest
  • python
  • react
  • reportlab
  • spacetimedb
  • typescript
  • uvicorn
  • vitest
Share this project:

Updates

Submission history