What happens after a document arrives
Extracting an invoice is a commodity in 2026 — a few US cents a document. So the product isn't in reading it. It's in the four things that come after: how it gets coded, when the system should stop, what kind of ledger it lands in, and how any of it is proven to an auditor.
Five stages
Intake
Each company gets its own forwarding address — suppliers send directly, colleagues forward. Phone capture is a web page, not an app to install. Platforms (Stripe, PayPal, WooCommerce) arrive by webhook; bank statements import as PDF or CSV. Every file is SHA-256 hashed on arrival, so the same document is never processed twice.
Extraction
A multimodal model returns a strict JSON schema with a confidence score on every field. That matters: an unreadable invoice number and a crystal-clear total are two different situations, and a reviewer needs to see the difference. High-value or low-confidence documents run through a second model — disagreement between two vendors is the strongest signal there is.
Resolution
Suppliers are matched fuzzily on name, tax ID and bank account. Duplicate detection is a weighted rule, not a single field: same supplier, amount within 0.5%, date within seven days, invoice-number similarity — all four together. A new supplier always goes to a human. We don't guess.
Coding
Not a general model trained on everyone's data. We embed "supplier + line description" and retrieve the nearest past entries from this company's own history, then propose the account, tax treatment and cost centre it used before. Usable after roughly fifteen documents per supplier — and explainable, which beats any confidence score.
Post, or hand over
Clear the gate and it writes a balanced double-entry journal into the ledger, with the source document and the evidence row attached in the same transaction. Fail the gate and it goes to the exception queue — stating why it is there.
The ledger is ours, so four rules live in the database
Checks in application code can always be bypassed by another code path. Database constraints cannot. These four are not settings and not options — they are operations the database itself refuses.
Journals must balance
Every journal, every currency: debits equal credits. A deferred constraint checks at commit time — being unbalanced mid-write is fine, being unbalanced at commit rejects the whole journal.
Append-only
Update and delete rights on the journal tables are revoked — the application itself cannot do it. Corrections are a reversal plus a correct entry; both stay on the books.
Locked periods reject writes
A before-insert trigger, not a service-layer if. Once a month is closed, nothing can slip an entry into it.
The evidence chain cannot break
Each evidence row's hash includes the previous row's hash. Alter history and the software reports exactly where the chain fails.
And one about money
Amounts are integers in minor units, never floats. Every journal line stores three values together: the original-currency amount, the FX rate used, and the converted HKD amount. Not two with the third computed later — recomputing silently rewrites history. The rate's source and timestamp go into the evidence row.
Why the gate needs two independent conditions
A model's self-reported confidence is not calibrated probability. Using it alone to decide whether to post unattended puts an accounting consequence on an uncalibrated number. So the other half of the gate has to be deterministic, auditable, and completely independent of the model.
| The model half | The deterministic half |
|---|---|
| Every extracted field clears its own threshold — not one overall score | Amount is below this company's auto-post limit |
| Where cross-check fired, both models agree | The supplier has enough and consistent history |
| — | Not a new supplier; not a suspected duplicate |
| — | Period unlocked; policy checks (budget, cost centre, approver) pass |
| Field | Opening value |
|---|---|
| Currency | 0.97 |
| Amount | 0.95 |
| Date | 0.93 |
| Supplier | 0.90 |
| Line items | 0.80 |
| Minimum supplier history | 15 postings, ≥80% to the same account |
| Auto-post ceiling | Derived from the company's materiality, not picked out of the air |
Expect 30–40% of documents to stop at launch
That is the design, not a failure. A high exception rate costs time; a wrong posting costs credibility, and those are not the same order of magnitude. Month one reviews everything, months two and three sample 25%, and 10% continues indefinitely.
Thresholds loosen only on evidence
After a run of auto-postings that sampling confirms correct, the system may propose lowering the threshold for that one supplier. There is no "we've got used to it, let's relax everything".
Individual size isn't the real risk — the aggregate is
The system continuously computes auto-posted value × sampled error rate = projected undetected error and compares it against the company's performance materiality. Above a quarter of it, the gate tightens automatically and warns. This is the answer to the question an auditor actually asks: how do you know nothing material went wrong.
Evidence is the actual deliverable
Because we borrowed nobody's credibility, the evidence layer is the only reason an auditor has to accept a machine-posted entry. It is not a nice-to-have and it is not added later — it exists at the same moment the ledger does.
- Inputs at transaction grain — pointing at that specific file, not a summary of a batch
- Model and prompt version — generative output isn't reproducible, so recording what was actually used matters far more than recording "we used AI"
- Per-field confidence — with the basis for the decision ("47 prior entries, all consistent")
- FX rate source and timestamp — every conversion can name where its rate came from
- Reviewer, timestamp, and what changed — a rubber-stamp approval records as "no fields changed", never hidden
- A hash chain — each row links to the previous one, so retroactive tampering is detectable
One rule with no exceptions
A posted entry has no edit button. If it's wrong you reverse it and post the correct one, and both stay on the books with a reason. Quietly editing a posted entry is precisely the action that destroys the whole evidence system — so we don't give ourselves that ability either.
Why we own the ledger instead of building on Xero
We considered building a layer on top and drew the architecture. We rejected it — not because the product isn't good, but because its data model was shaped in an era when nobody imagined a machine would be the primary operator.
| A reasonable assumption then | The consequence now |
|---|---|
| The interface is the product | The API is secondary; bank reconciliation is documented as not supported and not planned |
| Machines don't need an identity | Every API write records its user as System Generated — you cannot tell a reviewed entry from an unattended one |
| An audit trail need only record changes | It records what changed, never the basis on which it was judged |
| One company, one set of books | A practice managing three hundred clients means three hundred subscriptions |
None of these can be patched with a feature
They are foundations. Build on someone else's foundation and the few things that matter most stay out of reach — so the ledger has to be ours. The price is that we earn an auditor's trust from zero, which is exactly what the evidence layer is for.
Limits we won't hide
Version one has no live bank feeds — statements import as PDF and CSV. No payroll or MPF. No work-in-progress valuation for made-to-order production. No tax depreciation allowances; that one goes back to the auditor. These are not "coming soon" — they are explicitly out of version one.
Want to see it run on your documents?
We're looking for our first design partners. Nothing to pay; what we ask for is real documents and honest feedback.
We reply personally. No newsletter, no drip sequence — your address is used to talk to you about this and nothing else.