2026-08-25 · Guide

Bots for Finance: Reconciliation and Expense Review

The first week of the month is nine jobs pretending to be one. The bank feed has 412 lines. There are 90 receipts in a folder, another 30 in Slack, and some number that exist only as a photo on somebody's phone. Four invoices are past 60 days. A VAT deadline sits on the 7th, payroll on the 25th, and a founder wants the cash number by Wednesday.

Almost none of that is arithmetic. The arithmetic was solved decades ago. Your week is finding the fourteen things that do not agree, working out why, then making a small number of irreversible commitments on the back of it. That split makes finance an unusually good and unusually dangerous place to put bots: the reading half is enormous and mechanical, the committing half is where mistakes have signatures on them.

Break month end into five kinds of work before automating any of it

Split the week five ways and the automation question answers itself.

Matching. Bank and card lines against receipts and invoices. High volume, low judgment per line, the single largest consumer of your time.

Coding. Putting each transaction into the right account. Rules based, mostly boring, occasionally the exact place where the tax treatment is decided.

Chasing. Receipts from colleagues who lost them, payments from customers who have not paid. Repetitive, unloved, slipping.

Reviewing exceptions. Small in volume, large in value, and the actual job. Where an unrecognised subscription, a double charge, or a supplier quietly repricing you turns up.

Committing. Posting entries, releasing payments, submitting the filing. Minutes of work, irreversible, frequently attached to a named human by law.

There is no natural break in that list. The work slides from mechanical to irreversible along one gradient, with no moment where the tooling warns you that you crossed from reading into committing. That is why the boundary has to be written down rather than assumed.

Rank finance tasks by what a mistake costs to unwind

Volume is the wrong axis. Sort by what reversing a wrong output takes and who is standing there when you do it, and the list separates cleanly.

Finance taskCost to unwindWho finds outVerdict
Matching a line to a receiptRe-run itYouAutomate
Naming an unmatched line and whyRe-run itYouAutomate
Inventorying recurring chargesRe-run itYouAutomate
Drafting a receipt chaseDelete the draftYouAutomate, you send
Coding a routine transaction against a written ruleEdit the proposalYouAutomate as a proposal
Coding an unusual transactionAn amended return, years laterYour accountant or a tax authorityNever
Posting a journal entryA reversal, visible on both sides foreverAn auditorNever
Releasing or scheduling a paymentA recall request that may failThe vendor and your bankNever
Submitting a filingAn amendment, plus penalty and interestA tax authorityNever
Cancelling a subscriptionRe-subscribe, sometimes at a new priceYour team, mid-weekYou approve each one

The interesting row is the fifth. Coding against a rule you wrote is safe: the rule is yours and the output is a proposal. Coding a transaction that fits no rule is the top of the never list, because the transaction resisting your rules is precisely the one where the treatment is a decision. The same action sits on both sides of the line depending on whether a rule fired, which is why "the bot categorises transactions" is not a coherent permission and "the bot proposes a category when rule 4 fires" is.

Give the close six bots, none of which commits anything

The jobWhat the bot ownsWhere it stopsStart from
Statement to receipt matchingMatches by rule, names the reason when it cannotNever edits the live books, each change waits for your approvalBookkeeping Auditor
Recurring spend inventoryEvery repeating charge with amount, cadence, last price change, cancellation pathNever cancels or unsubscribes anything you have not individually approvedSubscription Pruner
Keep or cut analysisWeighs what a tool costs against evidence you use itNever cancels a subscription without your explicit approval of that oneSubscription Cancellation Advisor
Vendor price movementWatches published pricing so a renewal is not a surpriseOnly reads public pages, never fills forms or creates accountsCompetitor Pricing Watch
Receipt chasingFinds which expenses are missing paperwork and drafts the nudgeNever sends an email, every draft waits for explicit approvalInbox Triage
Cash and holdings viewAssembles the position and proposes what a rebalance would look likeNever trades or moves money, every rebalance is a recommendation to youPersonal CFO

Six listings, six jobs, one shared verb in the boundary column: none of them commits. That is not a coincidence, it is the only shape a finance bot can take and still be safe to leave running on a schedule.

Start with matching. Biggest time sink, and the one where a mistake costs a correction rather than a consequence, provided the boundary below holds. The line-level build, including how to write the matching rules, is in the expense reconciliation setup, and the recurring-charge sweep has its own walkthrough in the subscription audit.

Guard against the confident wrong match, not the missing one

A missing match costs you thirty seconds. A wrong match costs you a number you reported, and possibly signed.

Understand why the wrong match is the likelier error. A language model's least natural output is "I could not do this". Given a bank line reading SQ *TCFEE 04 and a folder of receipts, a plausible pairing is the path of least resistance, and it arrives with a fluent one-line justification. Two amounts nine pence apart, a date two days out, a descriptor resembling another vendor: each is a reason to stop, and each is easy to write past.

The counter is not better prompting. It is making a match a claim that requires evidence. Every match states which two records, the amount difference, the date difference, and which numbered rule fired. If no rule fired, there is no match, however obvious it looks to a reader. Amount tolerance is zero except for a named list of cases like currency conversion and card tips, and in those cases the difference is printed rather than absorbed. A receipt is consumed once, and a second attempt to use it is itself an exception.

That last rule does more work than it looks. Single consumption turns a duplicate charge from an invisible event into a reported one: the second charge reaches for a receipt that already matched and cannot have it. Without it, a supplier billing you twice reconciles perfectly and never appears anywhere.

Prove the matcher is not inventing, with tests that can fail

A match rate is not evidence on its own. A bot that guesses well posts a better match rate than one that follows your rules, which is the whole problem. Run these against a closed period you already reconciled by hand.

TestSetupPasses whenA failure means
Half receipts removedCopy a closed month, delete half the receipt filesMatch count falls roughly in proportionIt is pattern completing, not matching. Every figure it produced this year is suspect
Decoy receiptAdd a receipt from another month at an identical amountIt lands in DATE_OUT_OF_WINDOWThe date rule is decorative
Duplicate receiptCopy one receipt under a new filenameThe second use raises RECEIPT_ALREADY_USEDReceipts are being consumed twice and your totals are wrong
Truncated exportCut the last 40 rows from bank.csvThe gap is named in what it could not seeA clean report on incomplete data, which is the worst output of all
Rule attributionTake 10 matches, verify the cited rule by handAll 10 check outRules are cited decoratively, so no match is actually evidenced
Tolerance driftAlter one receipt by nine penceIt becomes AMOUNT_MISMATCHTolerance crept in and small errors are being absorbed silently

Run the first test before you trust a single number. The alarming result is a match count that barely moves, which means the bot is reconstructing plausible pairings from the bank line alone and the receipts were decoration. Rerun the first and last tests after every charter edit, because one reworded sentence is enough to move this behaviour. How to test a bot before you trust it covers the general discipline.

Ship the exception list, because that is the deliverable

Reframe what you are buying. Not the 380 lines that matched, because those never troubled you. You are buying a short, reasoned queue that turns two hours of archaeology into fourteen decisions of thirty seconds each.

So the format of that queue is the product. Sort by what it costs to be wrong rather than by count: one unknown 4,000 line outranks nine mystery card charges of eleven pounds, and a reporting bot's instinct is to lead with the biggest bucket. Then give every entry a reason code from a fixed vocabulary small enough to memorise, because each code implies a different action and a different permanent fix.

Reason codeWhat it meansYour move nowThe permanent fix
NO_RECEIPTNothing in the folder matchedAsk the spender, or accept itCapture at point of sale, not at month end
MULTIPLE_CANDIDATESTwo or more receipts fit the rulePick one, record whichSharper vendor mapping, narrower window
AMOUNT_MISMATCHAmounts differ, difference printedCheck for a tip or an FX spreadAdd the vendor to the named tip or FX list
UNKNOWN_DESCRIPTORDescriptor absent from mappings.csvIdentify it onceAdd the row to mappings.csv
RECEIPT_ALREADY_USEDA second line reached for a used receiptLook for a duplicate chargeChase the vendor, or fix the folder
DATE_OUT_OF_WINDOWReceipt sits outside the rule windowConfirm the delay is genuineWiden the window for that one named vendor
CURRENCY_UNRESOLVEDNo rate available for the dateSupply the ratePin a rate source in the charter

The fourth column is why the list shrinks month over month instead of repeating. An UNKNOWN_DESCRIPTOR you resolve once never returns. A NO_RECEIPT you resolve by nagging returns every month until you change how receipts are captured.

The list must also state what it could not see: the export that stopped on the 24th, the folder that was not shared, the currency it could not convert. That section is the difference between an exception list and a false clean bill of health, and it should carry the last row date of every file it read. One more piece of plumbing pays for itself immediately: a known-exceptions file you maintain, so the same accepted oddity is not re-raised until you stop reading the list.

Diagnose a bad reconciliation run from its symptom

Most of these look like the bot working well, which is why they survive months.

SymptomWhat is actually happeningThe fix
60 exceptions, all under 20Sorted by count, not by valueSort by amount descending, always
The same five exceptions every monthNo known-exceptions fileMaintain one, review it quarterly
Match rate jumped after a prompt tweakThe bot started reasoning past your rulesRe-run the half-receipts test before believing the number
A vendor charge appears twice, both matchedOne receipt consumed twiceEnforce single consumption, raise the second use
Your numbers disagree with the accounting systemIt read a stale or partial exportPrint the last row date of every source file, every run
A clean report, but the feed stopped on the 24thNothing reported the absenceRequire a coverage section with date ranges
Exceptions cite rules you never wroteRule numbers used as decorationMake the charter's rule list the only vocabulary allowed

The third row is the dangerous one, because a higher match rate reads as improvement to everyone including you. Treat an unexplained jump the way you would treat a supplier invoice that suddenly went down: pleasant, worth proving.

Write moving money, posting an entry, and filing as three separate lines

People collapse those three into "the bot cannot spend money". They are different actions with different consequences, and each needs its own sentence in the charter.

Moving money covers payments, transfers, refunds, card actions, and payroll runs. It also covers approving an invoice for payment, which is moving money with an extra click in between.

Posting an entry means writing to the ledger. This is the one people wave through, because a journal entry feels like data rather than money. It is not. A posted entry is a record you may have to explain years later, a reversal leaves both sides visible forever, and a book whose entries were authored by a model is a book you cannot fully account for. The bot proposes entries in a file. A person posts them.

Filing means submitting to a tax or government body: returns, payroll submissions, statutory accounts, contractor forms. A filing is a signed statement attached to a named human, and there is no story in which a bot files and you are not the one who answers for it.

Three sentences, not one, because a charter saying "never spend money" is satisfied by a bot that posts forty journal entries. Limits are read literally by the thing they constrain.

The runtime facts make this stricter rather than looser. An approval controls a proposed action and does not reverse completed work, so an approval prompt is not an undo button for a payment that has left. And there is no audit view of bot actions yet, which is exactly the wrong property for the one function in a company that runs on audit trails. Until there is one, you keep the trail: the bot appends findings to a dated file it may never edit, and last month's file is immutable.

Never sign a bot into a bank, and work from exports instead

One piece of platform reality decides your whole architecture here.

All bots on your account share one persistent cloud computer, assigned to your user account rather than to an individual bot. Browser cookies, signed-in sessions, files, and command line credentials are shared across every bot on it. Each bot gets its own screen, and the documentation is explicit that the screens are separate work surfaces and not separate security boundaries, and that you should not use separate bots as a security boundary. Deleting a bot does not remove the shared files or browser sessions it leaves behind.

Read that as a finance person. A signed-in banking session on that machine is available to everything on it, not just the bot you had in mind, and with no audit view you would reconstruct events by hand afterwards. So the rule is absolute: never sign a bot into a bank, a card portal, a payroll system, or a tax account. Not with read-only credentials, not "just this once".

Work from exports. You download the CSV or OFX yourself into a folder the bot reads. Where your accounting tool offers a genuine read-only seat, use that and nothing wider. Anything the bot needs to buy or trial goes on a dedicated card with a low limit, never the account that pays your suppliers. The wider version of this reasoning is in the shared computer security guide.

Two smaller facts point the same way. Bots run behind static egress IP addresses and some services flag datacenter addresses, banks among the most aggressive. And the correct behaviour at a step-up prompt is to stop: Flight Check-In states it plainly, stopping for a human at every two-factor prompt or captcha and never trying to get past one. A bank blocking your bot is not an obstacle to route around.

Answer the strongest objection: my accounting tool already matches these

The honest challenge comes from people who already run bank feeds and rules.

"My accounting software auto-matches most lines and suggests categories from history. It is deterministic, it is supported, and it is included. Why add a model to a job a rules engine already does?"

Most of that is right, and you should keep the rules engine. Deterministic bank rules beat a model at the thing they do, they are auditable, and every line they clear is a line nobody thinks about. Nothing here proposes replacing them.

The gap is at the other end. A rules engine optimises for match rate, and match rate is the wrong objective, because the lines it cannot clear land in one undifferentiated pile with no reason attached and no memory of last month. Nothing tells you the export was short, a receipt was used twice, or this same unknown descriptor has appeared four months running. The exception queue is the deliverable, and a rules engine treats it as residue.

So the shape that works is both. Rules clear what rules can clear, the bot takes what is left, applies your named rules, and produces the coded queue plus the coverage report. If your feeds already leave a short queue you genuinely read every month, you do not need a bot, and this article has nothing to sell you.

Keep these finance jobs off the bot entirely

Payments, in every form, including scheduling one for later and approving one for release.

Ledger writes. Proposals in a file, yes. Posting, no.

Filings and anything carrying a signature, including the letter to your accountant that becomes the basis of a filing.

Payroll. All of it. Not the calculation, not the file, not the submission, not the payslip email.

Policy questions dressed as data entry. Is this expense allowable, is this capex or opex, is this person a contractor or an employee. The answer will be fluent, confident, and blind to your jurisdiction, and being wrong lands years later with interest attached.

Coding anything unusual. Routine categorisation against explicit rules is fine. The transaction that does not fit a rule is exactly the one where the tax treatment lives, and it goes in the exception list rather than into an account.

Credit decisions and collections escalation. Calling a customer a bad payer and handing them to an agency is a business decision with a legal tail.

Any communication with an auditor, a bank, a regulator, or a tax authority. Chasing an unpaid invoice at least happens in your own voice to your own customer, and even that stays a draft, which the invoice chasing setup works through properly.

Deleting or merging anything it believes is a duplicate. Duplicates in books are findings, not chores.

And the one that looks harmless: writing the commentary in a board pack. Numbers with a narrative attached become the narrative, and the narrative is your judgment, not a summarisation task.

Paste this month end exception charter and adjust the rules

You are my Month End Exceptions bot. You find what does not agree.
You never touch money, the ledger, or a filing.

// TRIGGER
The 3rd of each month at 07:00 <IANA zone>, and on demand when I ask.

// WHAT YOU READ
- /finance/exports/<YYYY-MM>/bank.csv   (I export it, you never log in)
- /finance/exports/<YYYY-MM>/card.csv
- /finance/receipts/<YYYY-MM>/          (files I have put there)
- /finance/mappings.csv                 (descriptor to vendor, mine)
- /finance/known-exceptions.csv         (accepted, do not re-raise)
Nothing else. You never open a bank, card, payroll, or tax portal and
you never sign into a financial service.

// MATCHING RULES, IN ORDER. A match needs a rule to fire.
R1  exact amount + vendor from mappings.csv + within 3 calendar days
R2  exact amount + an invoice or reference number present in both
R3  exact amount + vendor + within 10 days, only for <named vendors>
No other rule exists. "It is obviously the same thing" is not a rule.
Amount tolerance is zero, with two exceptions: currency conversion,
where you print the converted amount, the rate, and the rate source;
and card tips, where you print both amounts. Each receipt is consumed
once, and a second use is itself an exception.

// OUTPUT
1. EXCEPTIONS, sorted by amount descending. For each: the bank line
   verbatim, one reason code, and what would resolve it. Use these
   codes exactly: NO_RECEIPT, MULTIPLE_CANDIDATES, AMOUNT_MISMATCH,
   UNKNOWN_DESCRIPTOR, RECEIPT_ALREADY_USED, DATE_OUT_OF_WINDOW,
   CURRENCY_UNRESOLVED.
2. MATCHED: a table of count by rule. No commentary on spending.
3. WHAT I COULD NOT SEE: every file that failed to open, every export
   that looks truncated, the last row date of each file, and any date
   range missing from the data.
4. NEW RECURRING: charges present this month and last at the same
   amount that are absent from mappings.csv.

// WHERE YOU STOP
You never initiate, schedule, approve, or cancel a payment, transfer,
refund, or payroll run.
You never write to the accounting system, post a journal entry, edit a
transaction, or change a category in the live books.
You never prepare or submit a filing to any tax or government body.
You never email a vendor, customer, accountant, bank, or auditor.
You never sign into a financial institution and never store a
credential.
You never delete or merge a transaction you believe is a duplicate.

Write findings to /finance/exceptions/<YYYY-MM>.md, append only. Never
edit a previous month's file.

If a task cannot be completed without crossing one of those lines,
stop, say what you would have done, and wait. Failing is the correct
outcome. Do not find another route to the same effect.

Run one close end to end: 412 lines, 120 receipts, 14 exceptions

Point it at a copy of last month first and compare its exception list against the one you built by hand. Here is four months of that, on the numbers from the opening paragraph.

StageWhat the run producedWhat it cost you
Dry run, closed month380 of 412 matched by rule, 32 exceptions40 minutes comparing against your own list
Month 1, live371 matched, 41 exceptions, 9 unknown descriptors35 minutes, mostly naming descriptors once
Month 2mappings.csv has 9 new rows. 388 matched, 24 exceptions20 minutes
Month 3known-exceptions holds 6 accepted oddities. 391 matched, 14 exceptions12 minutes
Month 3 findingsA duplicate 1,240 charge, and a supplier moved from 89 to 129Two real recoveries from one 12 minute queue

Two things there matter more than the match rate. The exception count falls because you fixed causes, not because the bot got cleverer, which is the only improvement that lasts. And both month 3 findings are things a human misses at 380 matched lines, which is the real argument: not speed, but attention pointed at the fourteen lines that deserve it.

Where this breaks down: entities, currencies, and audited books

Multiple entities. Intercompany transactions appear on both sides and reconcile against each other rather than against a receipt. A single-folder matcher misses them or matches them wrongly. Give intercompany its own reason code and let it fall out as an exception every time.

Multi-currency at scale. Printing the rate and its source covers a handful of foreign charges. It does not cover revaluation, realised and unrealised differences, or period-end rates, which are accounting decisions with a policy behind them rather than conversions.

Accruals and prepayments. Which period a cost belongs to is judgment about the substance of an arrangement. A bot can flag an invoice that spans a period boundary. It cannot allocate it.

Audited books. Auditors ask what happened and when. The runtime has no audit view of bot actions yet, so your evidence is the immutable dated file and nothing else. If that is not enough for your auditor, find out now rather than in March.

Cash and paper. If a meaningful share of spend never produces a digital receipt, your list is dominated by NO_RECEIPT and the bot is measuring a process problem. Fix capture first.

The adjacent problem, once the queue is short, is what happens to the items you hand back to a human and nobody picks up. How a bot should hand work back to a human covers the shape of a handover that survives a busy week.

Keep reading: Grok Bot for Designers, Grok Bot and HubSpot, Every Grok Bot Integration and What Each One Unlocks.

Frequently Asked Questions

Can AI bots do bookkeeping and reconciliation?

They can do the reading half well and should never do the committing half. A bot can match statement lines to receipts under rules you wrote, produce a reasoned exception list, inventory recurring charges, find missing paperwork, and draft the chase emails. It should not post entries, release payments, or submit filings. The useful mental model is a very fast assistant who prepares everything for review and has no authority to commit anything, which is also how the catalog listings for this work are written.

What should a finance bot never be allowed to do?

Three separate things, and they need three separate sentences in the charter. It never moves money, which includes payments, transfers, refunds, payroll, and approving an invoice for release. It never posts to the ledger, because an entry is a record you may have to explain years later and a reversal leaves both sides visible. And it never prepares or submits a filing, since a filing is a signed statement attached to a named person. Communication with banks, auditors, and tax authorities is out for the same reason.

How do you stop a reconciliation bot from making a wrong match?

Make a match a claim that requires evidence. Every match names the two records, the amount difference, the date difference, and the numbered rule that fired, and if no rule fired then no match exists regardless of how obvious the pairing looks. Keep amount tolerance at zero apart from named cases like currency conversion, where the difference is printed rather than absorbed. Then test for invention: re-run a closed month with half the receipts removed and confirm the match count falls proportionally rather than holding up.

Should a bot have access to your bank account?

No. Every bot on the account shares one persistent cloud computer, and browser cookies, signed-in sessions, and files are shared across all of them, with the documentation stating plainly that separate bots are not a security boundary and that deleting a bot does not remove its sessions. There is also no audit view of bot actions yet. Export the statements yourself and let the bot read files instead. If a bank challenges the bot with a step-up prompt, the correct behaviour is to stop, not to route around it.

Bots for Finance: Reconciliation and Expense Review | botskills.sh