§1The problem
Statements were scattered across different banking apps, each in its own format, and none showed the whole picture. I needed to see everything together, categorised, and know which recurring bills of the month were already paid.
§2What I built
- Bank connectionReal connection to banks through the Enable Banking API (Open Banking), with bank consent and automatic daily sync.
- Unified statementFiltered by sub-account (the app's unit is the sub-account, not the bank).
- CategorisationLearns rules from the corrections the user makes.
- Recurring billsExpected amount, “paid or not” detected from the statement, provisioning per “pocket” (reserve sub-account), monthly/bimonthly/four-monthly periods, multiple currencies with ECB rates.
- One-off importPayments brought in once from another of my apps, as a copy, so this app doesn't depend on it.
- Demo modeSample data (fixtures), without touching any bank.
§3The hard parts
5 entries- 01
Real Open Banking API integration
Consent, HTTPS-only redirects, the bank's daily query limit, and transient errors that the next sync clears.
- 02
Deduplication
Syncing again never duplicates transactions.
- 03
Detecting a paid recurring bill
Even when the bank's text doesn't match the name, by also using amount and a window of days.
- 04
Modelling sub-accounts and dates
Pocket name and account type come from different endpoints; the transaction date is used instead of the booking date.
- 05
SQLite schema migrations without data loss
For example, rebuilding period constraints.
§4Result
- transactions in the first import
- 1,000+
- inserted by the second sync (no duplicates)
- 0
Running in production since September 2026, with several real bank connections and automatic daily sync. The first import brought in over a thousand transactions, and a second sync inserted zero (deduplication working).
§5Stack
- Next.js 16
- React 19
- TypeScript 7
- Tailwind 4
- SQLite (better-sqlite3)
- Own design system
- Enable Banking (Open Banking / PSD2)
- pm2
- Daily scheduling
- Tailscale