Imports — bank statements and CSV
Being builtPartly works today — the limits are below.
Countbean imports a bank export by mapping each row to two accounts — the account the money moved through and the category it belongs to — so every imported transaction balances before it is written.
What works today
Hand the agent a file — CSV, TSV, OFX or QFX — and it reads the rows,
maps them to accounts, and writes balanced transactions. Anything it cannot
classify confidently is flagged ! and left pending rather than guessed.
This works because a parser reads the file, not the model. Both routes into
your book use it — the Claude plugin’s /countbean:ingest and the Telegram bot
alike — so:
- The column mapping is the same on every run of the same file, and the answer says which column it read as the amount.
- Each row gets a stable key, so re-importing an overlapping export does not double-book.
- The model’s job is the part that needs judgement: which account a line belongs to, and asking about anything the parser flagged.
⚠️ PDF, QIF and JSON are not among them. CSV, TSV, OFX and QFX have parsers; the others do not yet, and the bot refuses them in words rather than handing them to the model to squint at.
The rows never pass through the model either way. The file is stored on your book and parsed there; the agent is shown a summary — how many rows, the date range, anything flagged, and each payee once — and chooses an account per payee. The transactions are then written from the parsed rows, so a statement’s length is not limited by what a model can copy, and no amount is ever re-typed.
How a statement is read
A server-side parser, not the model. It returns the column mapping it used alongside the transactions it found, handles the three amount-column shapes below explicitly, and gives each row a stable key so a re-import cannot double-book.
That is the difference worth caring about: a parser gives the same answer on the same file every time, and tells you which column it read as the amount. A model reading columns does neither.
The three shapes an amount column comes in
| Shape | Looks like | The trap |
|---|---|---|
| Signed amount | -42.00 |
Some banks sign from their side |
| Split debit/credit | Debit: 42.00, Credit: |
Empty string, not zero |
| Running balance | Balance: 4,182.00 |
The amount is a difference between rows, so row order is load-bearing |
When a file has a running balance as well as amounts, the balance decides
which way the money moved, and any row it contradicts is flagged. A German
export that writes its debit column already negative (-657,62) is the case
that made this necessary: read by its column header alone, every debit came out
as a deposit.
What there is not
No bank feeds. No direct connection to any bank, and none planned. You export a file — which is also why every bank in every country works on your first day, with no integration list to be missing from.
Something here wrong or missing? Put it on the board — it is public, and the reply is in the thread.