Skip to content

Import statements

A whole statement enters Badlen through one route, POST /api/v1/import, reached with an API key carrying transactions:write. That is the entire surface. There is no CSV parser, no OFX or QIF reader, no bank connection, and no screen that takes a statement file: the web app’s only operation form writes a single line at a time.

In practice the route is driven by the MCP server, where your own Claude does the reading and the extracting. Driving it from a script of your own is the same route and the same key.

The route takes no raw file. It takes an account id and an array of normalised lines, and turning a broker file into them is the caller’s work.

Field Required Shape
date yes YYYY-MM-DD
amount yes Signed decimal string; negative is money out
label yes Non-empty text
currency no Three letters; EUR when absent
fee no Non-negative decimal string
tax no Non-negative decimal string

Money travels as a decimal string, never as a JSON number: a number is a double by the time the API reads it, and a quantity loses its last digits on the way in. A body that sends one is refused.

The schema also accepts type, counterparty and category, but the import does not write them onto the line. A batch’s categories are only counted: the dry run below reports how many lines arrived without one, so you can press Recategorize on Settings → Categories afterwards.

Each line gets a fingerprint built from its date, its amount and its label folded to lower case. A line whose fingerprint the account already holds is skipped, which is what makes re-sending the same statement safe.

Two identical operations on one day (two card payments of the same amount to the same merchant) are not a duplicate of each other. They are ranked apart within the batch, so both land. That ranking is the batch’s alone, which has one consequence worth knowing:

An operation you deleted after an earlier import is remembered. Badlen keeps a gravestone for the fingerprint, and the same line in a later import of the same statement is not written back.

POST /api/v1/import/reconcile takes exactly the same body and writes nothing. It answers with the split of the batch (how many lines are new, how many are already there, how many are buried), plus its date range, the inflow and outflow totals, how many operations the account already holds and up to what date, and the lines that match an existing date and amount under a different label. It also returns the questions worth putting to you before committing.

Both calls count through the same function and the import answers with the same figures, so what the dry run announces is what the write does.

Privacy