Reconciling a month without a spreadsheet
Reconciling a month means proving your books and your bank agree on one number on one date — which in a plain-text ledger is a single assertion line that the validator either accepts or refuses.
The spreadsheet version of reconciliation is two columns side by side, a highlighter, and an hour of eye-work that ends with a difference of $18.40 and no idea where it came from. It is not a bad method. It is just doing by hand the one thing a computer is unambiguously better at.
Reconciliation is not a comparison of two lists. It is a single claim, tested.
The claim
2026-09-01 balance Assets:Bank:Checking 4182.03 USD
That line says: at this date, this account holds exactly this. Run the validator and one of two things happens. Silence, and the month is reconciled. Or a failure telling you the computed balance, the asserted balance, and the difference between them.
There is no highlighter, no second column, and no judgement involved. You take the closing balance off your statement, you write it down, and the ledger argues with you or it does not.
The date trap, which everyone hits once
A balance assertion in Beancount applies at the beginning of the date it carries. It includes everything before that day and nothing on it.
So for an August statement closing on 31 August, you assert on 1 September, not 31 August. Assert on the 31st and you have excluded that day’s transactions, the assertion fails, and you spend twenty minutes hunting a discrepancy that is the last day of the month sitting exactly where it should be.
Write it as “the balance on the morning after” and it stops being confusing.
When it fails
An assertion failure gives you a number, and that number is nearly always one of four things.
| The difference is… | Almost certainly |
|---|---|
| Exactly one transaction’s amount | A missing entry, or a duplicated one |
| Divisible by 9 | A transposition — 48.10 typed as 41.80 |
| Round and recent | An in-flight payment the bank has not settled |
| Small and stubborn | A fee, or an FX difference on a converted charge |
The divisible-by-nine trick is genuinely old bookkeeping folklore and genuinely works: swapping two digits always produces a difference that is a multiple of nine.
For the first two, the ledger will find it faster than you will:
SELECT date, narration, position
WHERE account = 'Assets:Bank:Checking'
AND year = 2026 AND month = 8
ORDER BY date
Put that next to the statement and the missing or doubled row is visible in seconds, because both lists are now in the same order in the same shape.
Assert more than once a month
The reason people dread reconciliation is that they do it annually, so a failure means the error is somewhere in twelve months. An assertion every month turns the same failure into “it is somewhere in these thirty days”, and the search space is what makes this painful, not the arithmetic.
They are cheap. Twelve lines a year per account:
2026-08-01 balance Assets:Bank:Checking 3960.55 USD
2026-09-01 balance Assets:Bank:Checking 4182.03 USD
2026-10-01 balance Assets:Bank:Checking 4506.12 USD
Every one of those is checked every time the book is validated, forever. A mistake introduced later that would break an old month cannot slip in unnoticed — which is the difference between reconciliation as an event and reconciliation as a property of the file.
Starting from a balance you cannot explain
Opening a book mid-year means account balances with no history behind them. There is a directive for exactly this, and it is honest about what it is doing:
2026-01-01 open Equity:Opening-Balances
2026-01-01 pad Assets:Bank:Checking Equity:Opening-Balances
2026-01-02 balance Assets:Bank:Checking 3120.00 USD
pad inserts whatever transaction is needed to make the next assertion true,
and books the other side to equity. Used at the start of a book that is
correct. Used mid-year to make a stubborn difference disappear, it is a lie
with a directive around it — the number now matches and the reason is gone.
If a month will not reconcile, the answer is a missing entry, not a pad.
Where this sits in the product
- Balance assertions are ordinary ledger lines and are checked by
bean-checkon every write, so a book that does not reconcile cannot be committed over. Shipped - The agent can write assertions, run the queries, and read back the difference when one fails. Shipped
- BQL and the browser ledger are available on every plan, including the free one. Shipped
The honest edge
- Nothing reconciles automatically. There are no bank feeds and none planned Not built, so the closing balance comes off a statement you fetched. The assertion is proof, not a robot.
- Duplicates are the failure mode this catches most, and the importer that prevents them is still being built. Building Today the model reads the file, so re-importing an overlapping export can double-book. Assert after every import, not once at year end.
- A wrong entry buried in history has to be fixed forward. Revert works reliably on the tip only; reverting a middle commit fails on a git conflict. Shipped The workaround is a compensating entry, which is how real accounting handles it anyway — but it is a limitation, not a philosophy.
- No automatic FX rates. Not built A multi-currency account reconciles against the rate you or the agent recorded, and if the bank used a different one, that gap is yours to book.