Bank reconciliation
Reconciliation is proving that what the accounting file says happened in a bank account is what the bank says happened. Every month that goes unreconciled makes the numbers underneath it fiction, and a business running on unreconciled books cannot tell profit from cash.
What you can and cannot do
Be clear about this before starting, because the boundary is not obvious.
You can: find unreconciled transactions, spot duplicates and likely matches, identify uncoded and miscoded spending, compare a closing balance against a statement figure the customer gives you, and produce a worked list of exactly what a human should click.
You cannot: press the reconcile button. Neither Xero nor QuickBooks exposes bank feed statement lines for matching through their APIs. The green tick is a person in the web app, and no amount of API access changes that.
So the deliverable is a prepared reconciliation: every decision made, every exception explained, nothing left for the bookkeeper except confirming. That is most of the work and all of the tedium. Say so plainly rather than implying the reconciliation is done.
The sequence
1. Establish the account and the period
Ask, do not assume: which bank account, and to what date. A business with four accounts reconciled to four different dates is normal, and reconciling the wrong one wastes the whole exercise.
Then pull the account list and confirm the one you were given exists and is a bank account, not a clearing account someone named like one.
2. Get the closing balance from both sides
The accounting file’s balance comes from the balance sheet as at the period end date. The bank’s balance comes from the customer, off a statement. If those two numbers agree, the rest is verification. If they do not, the difference is what you are hunting.
Note the difference before you go looking. A difference that is exactly one transaction is a different investigation from a difference of 37 cents, and a difference that equals twice a transaction amount is almost always a sign reversed.
3. Pull the unreconciled transactions
Everything sitting in the accounting file that the bank has not confirmed. See the per vendor reference for the exact call:
4. Classify what you find
Every unreconciled item falls into one of five buckets, and the bucket decides the action. This is the part that needs judgement rather than a query, and it is covered in references/classifying-exceptions.md.
| Bucket | Typical cause | Action |
|---|---|---|
| Timing | Payment made near period end, cleared after | Note it, expect it next period. Not an error |
| Unpresented | Cheque or transfer that never cleared | Chase if it is old. Over six months, it probably needs writing back |
| Duplicate | Same transaction entered twice | Flag for deletion. Never delete it yourself without asking |
| Uncoded | Money moved but nobody said what for | Propose a code, do not guess silently |
| Genuine mismatch | Wrong amount, wrong date, wrong account | Investigate individually |
5. Deal with the uncoded
The largest category in most small business files. Money left in a suspense or uncategorised account because nobody knew what it was.
Propose a coding for each one with your reasoning visible, and group them so the customer can approve twenty at once instead of one at a time. Where you genuinely cannot tell, say so and ask. A confidently wrong code is worse than a question, because it silently misstates the accounts and nobody ever revisits it.
6. Report
The output is a short document, not a dump. See references/the-report.md for the shape. Lead with the difference and whether it is explained, then the exceptions that need a decision, then the routine items.
Judgement rules
- Never change a reconciled transaction. It has been agreed with the bank. Changing it breaks the reconciliation behind it and the person who reconciled it will not know why their numbers moved
- Never delete anything. Propose deletions, list them, and let a human execute. This is a one way door in an audited record
- Round numbers are suspicious. An exactly round payment to an unfamiliar payee deserves a look, not a code
- A difference that is a multiple of nine is very often two digits transposed. Check for it before hunting anything else
- Do not close a period to make it balance. If it does not reconcile, say it does not reconcile
Locked periods
Most files have a lock date, before which nothing can be edited. If the exceptions you find sit before it, editing them is not merely blocked, it is the wrong action: those periods have been reported on and possibly filed with a tax authority. Surface them as a separate list headed by the fact that they are locked, and let the accountant decide.
Reference files
Everything the skill tells your AI to read, exactly as it ships in the zip.
references/classifying-exceptions.md 4.0 KB
# Classifying what you find
Every unreconciled or unexplained item is one of five things. Getting the classification right is most of the value, because it decides who has to act and how urgently.
## Timing differences
A transaction correctly recorded that the bank has not processed yet. A payment run on the 31st clearing on the 2nd.
**Action:** none. Note it, and expect it to clear next period.
**How to tell:** it is dated within a few days of the period end and the amount matches something real. If a timing difference is more than a month old it is not a timing difference, it is an unpresented item.
## Unpresented items
A payment recorded that never actually happened: a cheque never banked, a transfer that failed, a payment to a closed account.
**Action:** chase. If it is over six months old, it probably needs writing back, and that is an accountant's decision, not yours.
**How to tell:** it is old, and repeated period after period in the same reconciliation. A stale item that has appeared in six consecutive reconciliations is the single clearest sign a file has never been properly cleaned.
## Duplicates
The same transaction entered twice, usually because it came in through both a bank feed and a manual entry, or because an import ran twice.
**Action:** flag for deletion, with both record identifiers, and let a human delete. Never delete one yourself unless explicitly told to, naming the specific record.
**How to tell:** same amount, same or adjacent date, same or similar description. Beware three false positives:
- A genuine repeated payment. A weekly subscription of the same amount is not a duplicate
- **A transfer between the business's own accounts**, which correctly appears twice, once on each side
- A payment and its refund, which have the same amount and opposite signs
Check the counterpart before calling anything a duplicate.
## Uncoded and miscoded
Money that moved with no useful account against it, sitting in suspense, uncategorised expense, or the wrong account entirely.
**Action:** propose a coding, with reasoning, grouped so they can be approved in batches.
**How to tell:** the account is a suspense or uncategorised one, or the coding is implausible given the payee. A payment to a fuel company sitting in professional fees is miscoded even though it is coded.
Base a proposal on evidence, in this order:
1. How the same payee was coded previously in the same file. This is the strongest signal and it costs one query
2. What the payee obviously is
3. What the amount and frequency suggest
Never invent a code from the amount alone. If the evidence runs out, ask. Write the question so it can be answered in one word.
## Genuine mismatches
Wrong amount, wrong date, wrong bank account, or a transaction that exists on one side and not the other.
**Action:** investigate individually and report. These are the ones that need a human, and they are usually few.
**How to tell:** everything else has been eliminated and there is still a difference.
## Arithmetic that saves time
Before hunting line by line, test the difference itself:
| The difference is | Look for |
|---|---|
| Exactly one transaction amount | That transaction, missing or duplicated |
| Twice a transaction amount | A sign reversed. A receipt entered as a payment |
| Divisible by 9 | Two digits transposed. 45 entered as 54 |
| A round number | A manual entry or an opening balance |
| Small and odd, under a dollar | Rounding, or a fee not recorded |
| Exactly a known fee amount | A bank fee nobody entered |
Running these six checks takes a minute and resolves a good proportion of differences outright.
## What to escalate rather than solve
- Anything in a locked or closed period
- Anything that changes a previously reported figure, particularly one that has gone to a tax authority
- A difference you cannot explain after working through the whole list. Say you cannot explain it. Do not force a balancing entry to make it look reconciled
- Transactions that look like they should not be in a business account at all. Report the fact, not a conclusion about it
references/quickbooks.md 3.4 KB
# Reconciliation in QuickBooks Online
## Find the account
```
quickbooks_list_accounts
```
Filter to `AccountType` of `Bank`. Note the `Id`. As in any file, check for accounts named like bank accounts that are really clearing accounts.
## Closing balance
```
quickbooks_get_balance_sheet as at the period end date
quickbooks_get_trial_balance the same number in context
```
QuickBooks accounts also carry a `CurrentBalance`, which is the running balance rather than the balance as at a date. For a reconciliation you want the report, not the field.
## Transactions on the account
```
quickbooks_get_general_ledger everything that hit the account, with dates
quickbooks_get_transaction_list filterable transaction listing
```
`quickbooks_get_general_ledger` is the workhorse. It gives you the lines that make up the balance, which is what you need to explain a difference.
For the transactions themselves:
```
quickbooks_list_deposits
quickbooks_list_expenses
quickbooks_list_transfers
quickbooks_list_payments
quickbooks_list_bill_payments
```
## Reconciliation status
QuickBooks does not expose the reconcile status of a transaction in a way you can filter on reliably, and it does not expose bank feed items awaiting matching. This is a harder limitation than Xero's, where at least `IsReconciled` is readable.
In practice this means a QuickBooks reconciliation review is built from the general ledger and the reports rather than from a reconciled flag. Work from the difference between the report balance and the statement balance, and account for it transaction by transaction.
## Reports worth pulling
| Tool | Use |
|---|---|
| `quickbooks_get_general_ledger` | The account's transactions, in order, with running balance |
| `quickbooks_get_transaction_list` | Filterable, good for date ranged slices |
| `quickbooks_get_profit_and_loss` | Context for whether a coding looks plausible |
| `quickbooks_get_account_list_report` | The chart of accounts, to propose a coding against |
## Querying directly
QuickBooks exposes its own query language, and the connector surfaces it. When the listing tools do not give you the filter you need, a query is usually the shorter path than pulling everything and filtering afterwards. Aggregate in the query where you can.
## Making a correction
```
quickbooks_update_expense / update_deposit / update_transfer to recode
quickbooks_create_journal_entry correcting entry
```
Prefer a journal entry over editing an original in any period that has been reported on. It leaves the history intact.
## Things that will catch you
- **`SyncToken` is mandatory on update, and must be current.** Read the record immediately before updating it. A stale token fails the write, and the failure is a version conflict, not a validation error, so do not treat it as a bad payload
- **Sparse update is the safe mode.** A full update replaces the object, so omitted fields are cleared. Send a sparse update unless you deliberately intend to replace everything
- **Closing the books sets a password protected cutoff.** Writes before it fail. Do not work around it
- **Fault codes carry the real reason, not the HTTP status.** The connector surfaces both, plus the `intuit_tid`. Quote the tid when reporting a failure, because Intuit support cannot trace anything without it
- **Voiding is not deleting.** A voided transaction stays in the ledger at zero. That is usually what you want in a reconciled period
references/the-report.md 2.7 KB
# The reconciliation report
The output of a reconciliation review is a short document a bookkeeper can act on in ten minutes. Not a transaction dump, and not a narrative.
## Shape
```
Bank reconciliation: <account name>, as at <date>
Balance per accounting file $X
Balance per bank statement $Y
Difference $Z <- explained / NOT explained
Needs a decision (N items)
1. ...
2. ...
Proposed codings (N items, $ total)
<grouped by proposed account>
Timing differences (N items, $ total)
<one line each, no action needed>
Stale items (N items, over 6 months)
<one line each, recommend write back>
```
## Rules for it
**Lead with the difference and whether it is explained.** That is the only number that decides whether the reconciliation can be signed off. Putting it after three screens of transactions is the most common way these reports get ignored.
**Separate decisions from information.** Two lists: things a human must choose about, and things they just need to know. Most reconciliations produce two or three real decisions and forty items of information, and mixing them buries the decisions.
**Group the proposed codings by target account**, not by date. Twenty fuel receipts approved as one group is one decision. Twenty individual lines is twenty.
**One line per item.** Date, amount, payee, and the specific thing you want. Not a paragraph.
**Show your reasoning only where it is not obvious.** "Coded to Motor vehicle expenses, same as the last 14 payments to this payee" is worth a line. "Coded to Bank fees" against a payee called Monthly Account Fee is not.
**Never round the difference.** If it is 37 cents, it is 37 cents. A rounded difference in a reconciliation is a contradiction in terms.
## Tone
The reader is usually a bookkeeper or an owner who does their own books. They are not stupid and they are busy. Write it the way a competent colleague would hand over: direct, no hedging, no apology for asking, and no accounting jargon where a plain word works.
Do not congratulate. Do not editorialise about the state of the file. If the file is a mess, the list of stale items says so more convincingly than a sentence about it would.
## Signing off
Say explicitly which of these is true, in these words:
- **"This reconciles."** The difference is nil or fully explained by timing differences you have listed
- **"This reconciles once the items above are actioned."** The difference is explained but needs someone to act
- **"This does not reconcile. $X is unexplained."** You could not account for the difference
Never imply the first when the third is true. An unreconciled account reported as reconciled is worse than one reported as broken, because nobody looks at it again.
references/xero.md 3.3 KB
# Reconciliation in Xero
## Find the account
```
xero_list_accounts
```
Filter to `Type` of `BANK`. The account you want carries a `BankAccountNumber` and a `Code`. Note the `AccountID`, everything below needs it.
Watch for accounts named like bank accounts that are not: clearing accounts, undeposited funds, and old accounts left active after a switch. Confirm the one you were given by its account number, not its name.
## Closing balance
```
xero_get_balance_sheet_report date: "2026-08-31"
```
Read the bank account's line. This is the accounting file's view. Compare it to the statement figure the customer gives you.
`xero_get_trial_balance_report` gives the same number alongside everything else, which is more useful when more than one account is in question.
## Unreconciled transactions
```
xero_list_bank_transactions
```
Every bank transaction carries `IsReconciled`, a boolean. The unreconciled set is what you are working through.
Also relevant and not in that list:
```
xero_list_invoices awaiting payment, both ACCREC and ACCPAY
xero_list_payments payments applied to invoices
xero_list_credit_notes credits not yet allocated
```
An invoice marked paid in Xero whose payment has not cleared the bank is a timing difference, not an error, and it will not appear as an unreconciled bank transaction.
## Uncoded and suspense
Xero does not expose the bank feed's unreconciled statement lines through the Accounting API at all, which is the single most important limitation here. What you can see is transactions already in the file.
For miscoded spending, pull the account transactions for the suspense or uncategorised account and review what has landed there:
```
xero_get_account the suspense account
xero_list_journals journal lines, for tracing
```
`xero_list_journals` is the ledger level view and is the right tool when you need to see exactly what hit an account and when. It paginates, so page through rather than assuming the first response is everything.
## Making a correction
Only ever on unreconciled items, and only when asked.
```
xero_update_bank_transaction to recode
xero_create_bank_transaction for a genuinely missing transaction
xero_create_manual_journal for a correcting entry
```
A manual journal is the right instrument for a correction that must not disturb the original record, which is most corrections in a period that has been reported on. It leaves an audit trail; editing the original does not.
## Things that will catch you
- **Reconciled transactions are effectively frozen.** Xero will let the API touch some of them. Do not. The reconciliation behind them is an agreement with the bank
- **A lock date silently blocks writes** before it. The error names the lock, so read it rather than retrying
- **Bank transfers are their own type.** A transfer between two of the business's own accounts appears on both sides and is a common source of apparent duplicates. Check `xero_list_bank_transactions` for the paired entry before flagging one as a duplicate
- **Dates are the transaction date, not the cleared date.** Xero holds no cleared date, so period end timing differences have to be reasoned about rather than queried
- **Multi currency accounts** report in both the account currency and the base currency. Make sure you are comparing like with like before declaring a difference