Built, not bought
Same house rule as the last money entry: no real numbers here. Every transaction, merchant and figure below is invented for illustration. The build details and the API behaviour are real; the money isn’t.
The export was the tell
Last month my wife and I took a trip up to New York, and afterwards I wanted to know what it had actually cost. So I logged into Monarch, exported a CSV, handed it to Claude, and got back a clean breakdown of the whole trip.
The analysis was genuinely good. The workflow was absurd. I was paying a subscription for the privilege of manually exporting my own transactions so that a model could read them — and the moment the file hit my desktop it was already stale.
That data was never Monarch’s. It comes out of my accounts, through Plaid, over an API I can call myself. The subscription wasn’t selling me the data. It was renting me a window onto it, and charging me to open the window by hand.
So the question stopped being which budgeting app is best and became why is there an app in the middle at all?
An afternoon, not a project
FastAPI, SQLite, and Plaid Link. Somewhere around 1,500 lines across Python, a little plain JavaScript and one page of Bootstrap. No framework, no ORM, no queue. It runs in a dev container today and it’ll be a container on the lab by the weekend.
The first connection was my credit union — deposit accounts, a credit card, a couple of loans — and the first sync came back with only thirty days. That looked wrong, because I’d asked for two years. It wasn’t. Plaid hands you a shallow window immediately and backfills the real history asynchronously; a second sync a few minutes later pulled in the rest, and two years were sitting on my disk. A few investment accounts went in after that.
Here’s the shape of the register — with sample rows in place of mine:
| Date | Description | Category | Amount |
|---|---|---|---|
| 2026-09-12 | Corner Coffee Co. pending | Food & drink / coffee | −5.00 |
| 2026-09-12 | Example Department Store | General merchandise | −250.00 |
| 2026-09-12 | Sample Grocery Delivery | Food & drink / groceries | −200.00 |
| 2026-09-11 | Payroll deposit | Transfer in | +1,000.00 |
| 2026-09-11 | Transfer to loan ••0000 | Loan payments / mortgage | −1,000.00 |
One decision shaped the whole thing: store everything. An aggregator decides in advance which fields are worth keeping, and you live with that decision forever. So every documented Plaid field gets its own typed column — fifty-one of them on a transaction — and every row also keeps the untouched API response as JSON alongside it. The export gave me four fields per transaction. Plaid returns fifty, including a geocoded location, a stable merchant identifier, the raw card-network description, and a confidence score on its own categorisation. None of that survived the CSV.
Build versus buy just collapsed
I’ve spent twenty years running that decision professionally. It has a well-understood shape: buying gets you a feature set someone else chose, on someone else’s roadmap, and you close the gap with process. Building gets you exactly what you need and a permanent maintenance obligation. Below a certain size, buy wins on cost every single time.
That calculus is what just moved — the build side fell through the floor for software at personal scale.
And it changes the thing, not just the price. You stop shopping. You stop comparing tiers, reading changelogs hoping the one feature you need made it in, migrating to a competitor for a single capability, and living inside the gap between what the product does and what you actually wanted. You add the feature. Tuesday. Because you needed it Tuesday.
That’s what makes this a problem for SaaS rather than a hobby. My app knows my actual account structure — which transfer is really the mortgage, which accounts are shared and which aren’t, how spending gets split between them. No product will ever ship that, because it’s a market of one. It isn’t a better budgeting app; it’s software that’s personal, in a way a product categorically can’t be.
The part I can’t answer yet
Here’s what I genuinely don’t know, and I’d rather write it down now than pretend later that I saw it coming.
Can a model build software that can be maintained and enhanced — or does every change turn into a rewrite? A rewrite is survivable when it’s a web page. It is not survivable when there’s a database underneath holding the only copy of years of history. Rewrites lose data. Schema churn corrupts it quietly. Errors don’t announce themselves, and there’s nobody to escalate to.
One afternoon isn’t evidence. But both columns already have entries:
| In favour | Against |
|---|---|
| A requirement changed mid-build — halfway through I decided I wanted every field, not a curated subset. That was an edit, not a restart. | A file holding a secret slipped past .gitignore into the first commit. Caught in minutes and scrubbed — but that's exactly the class of error speed produces. |
| Three missing fields turned up by diffing the API response against my own schema, and were backfilled from raw JSON already on disk. No re-pull, no gap in history. | Velocity hides review. Those ~1,500 lines landed faster than I could meaningfully read all of them. |
| Migrations are additive by default — new columns get added to the existing file rather than the file being rebuilt. | No vendor, no support contract, no second pair of eyes on the code. Ever. |
| Never discard the source payload and most future mistakes stay recoverable. The architecture that makes it cheap to enhance is the one that makes data loss unlikely. | Upstream will move. When Plaid changes a product or deprecates a field, nobody ships me a compatibility note. |
Balance: undetermined. The confident takes in both directions are running well ahead of the evidence, mine included. The only way to find out is to run one of these for real, on data I actually care about, and see what it looks like in six months and in eighteen.
The dashboard is scaffolding
There’s a web UI — account cards, a filterable register, click any row and every stored field comes up beside the raw JSON. It’s useful and it is not the point.
The point is the MCP server that goes on top of the database. I already run MCPs against the cars and the house, and the pattern holds: once Claude can query the source directly, the application layer mostly evaporates. What did that trip cost? stops being an export, an upload and a prompt, and becomes a query against live transactions with every field intact.
That’s the version I wanted last month, and the CSV was never going to get me there.
Next: the investments product so the brokerage accounts report holdings instead of nothing, webhooks so updates arrive on their own, the move onto the Docker host, and proper encryption for the tokens. Then the same exercise pointed at the health and diet data.
The subscription took four years. The replacement took an afternoon. Ask me again in eighteen months.