If you run a budget in a spreadsheet, you already know the worst part. It isn’t setting up the formulas, or even the monthly review. It’s sitting there tagging each transaction with a category so your actuals line up with your budget.
I do this for my household, and for a long time it was the single most annoying chore in our finances. So I built a system that does almost all of it for me. Here’s exactly how it works, including the parts that aren’t magic.
The setup, and the real problem
My wife and I run all of our spending through one credit card, because it earns the best points. The catch: it’s her personal card and I’m an authorized user, so I can’t log in to see the transactions myself. For months, “doing the budget” meant asking her to export a CSV so I could import it. Not a great system.
I now sync those transactions directly with SheetLink, which connects the card through Plaid (so SheetLink never sees her bank login) and drops the transactions into our Google Sheet. That solved the access problem. But it surfaced the real one.
SheetLink gives me everything the bank exposes, including Plaid’s category for each transaction. The trouble is that Plaid’s categories aren’t my budget categories. Plaid says GENERAL_MERCHANDISE. My budget says Groceries, or Kids, or Misc, depending on what we actually bought. Bridging that gap, transaction by transaction, was the chore.
The insight: most of it isn’t a judgment call
When I looked at the transactions we had already categorized, a pattern jumped out. I had tagged about 890 transactions by hand over the past year. Across them were roughly 400 unique merchants. And here’s the thing: about 95 percent of those merchants map to exactly one budget category, every single time.
- Spectrum is always Spectrum.
- Netflix is always Netflix.
- Our grocery store is always Groceries.
- The mortgage payment is always Mortgage.
These aren’t judgment calls. They’re the same decision, repeated forever. A lookup table handles them perfectly, with no AI required.
Only about 5 percent of merchants are genuinely ambiguous, and they’re ambiguous for real reasons that a human would also have to think about. Amazon could be Kids, Household, Entertainment or Misc, depending on what was in the box. 7-Eleven could be Gas or a snack run. Kohl’s and Michaels could be Kids or Entertainment. That 5 percent is the only place judgment actually adds value.
The system: lookup first, AI only for the gaps
The system has three layers, and they run in order.
1. Lookup table (about 95 percent). Learn a merchant-to-category map from the transactions I’ve already tagged. Every repeat merchant gets categorized instantly, with zero effort.
2. AI judgment (the new merchants). When a merchant shows up that the lookup table has never seen, that’s where an AI like Claude earns its place. Give it the merchant name, the amount, the bank’s category and my list of budget categories, and it reasons it out. A run of charges at a stadium plus concessions is clearly an Entertainment outing. A resort plus a museum plus a ballgame is the family trip we took. Crucially, every decision the AI makes becomes a new lookup rule, so it never has to decide that one again.
3. A quick human glance (the truly ambiguous). The Amazon-type charges get a best guess and a flag. I glance at those, and only those.
When I ran this on a recent batch of my real transactions, the lookup table handled the bulk instantly, the AI categorized the new merchants and turned each into a permanent rule, a handful got flagged for a two-second confirm, and transfers and card payments were correctly left alone because they aren’t budget expenses. The transactions that genuinely needed me to think? Two. Down from a screen full.
Why this beats “let AI do everything”
It would be easy to point an AI at every transaction and let it categorize the whole list. People do this. I think it’s the wrong design, for two reasons.
First, it’s wasteful. You don’t need a language model to know that Netflix is Netflix. A lookup table is faster, free, and never wrong on the repeats.
Second, and more importantly, the system gets lazier over time, not more dependent on AI. Every new merchant the AI categorizes becomes a rule. Next month, those merchants are pure lookup. The slice that needs AI shrinks every cycle. That’s the opposite of most “AI does your budget” tools, which re-guess everything every time.
The right mental model: a lookup table for the 95 percent that repeat, AI for the few that need a brain, and your own eyes on the genuinely ambiguous handful.
What makes it work: structured data Claude can read
Here’s the part that makes this more than a spreadsheet trick. SheetLink doesn’t only write to a Google Sheet. The CLI can output your transactions as structured data in formats built for machines, not just spreadsheets. That’s what turns your bank history into something an AI can reason over directly.
Install the CLI and sign in. sheetlink auth opens a Google sign-in in your browser. For scripts that run on their own, save a Max API key (it starts with sl_) with sheetlink auth --api-key, and it’s stored locally so you never pass it again:
npm install -g sheetlink
sheetlink auth # sign in through your browser
sheetlink auth --api-key sl_... # or save a Max API key for unattended runsJSON: pipe your transactions straight to a script or Claude
JSON is the default. It streams to stdout, so you can pipe it anywhere. This is the format I hand to Claude for categorization.
# all transactions as JSON
sheetlink sync --output json
# count them with jq
sheetlink sync --output json | jq '[.items[].transactions[]] | length'
# pipe straight into your categorizer (or Claude via a script)
sheetlink sync --output json | python3 categorize.pyThe shape is predictable: a top-level items array (one per connected bank), each with a transactions array. Clean, structured, easy for any AI or script to walk.
Here’s the part that makes it click. I run this inside Claude Code, so the CLI and Claude share the same terminal. I never export a file, upload anything, or paste a wall of JSON into a chat window. I just tell Claude to run sheetlink sync and it reads the output directly, then builds and runs the categorization against it. The data goes from my bank, through SheetLink, into a model that can act on it, with no file to export or upload.
SQLite: a local database Claude can run SQL against
With Max, output to a SQLite file and you have a real, queryable database of your finances on your own machine. Point Claude at it and ask questions in plain English; it writes the SQL.
# upsert all transactions into a local SQLite db
sheetlink sync --output sqlite:///path/to/finances.db
# then, for example:
# "how much did we spend on groceries each month this year?"
# Claude writes and runs the query against finances.dbCSV: portable and AI-readable
Want a flat file to drop into a sheet, a notebook or an AI tool? CSV is one flag.
# snapshot CSV (overwrites each run)
sheetlink sync --output csv --file ~/finances.csvPostgres: for when this grows up
If you’re building something real on top of your transactions, sync straight into Postgres (also part of Max). Same command, a connection string instead of a file.
sheetlink sync --output postgres://localhost/financesThis is the difference between a sync button and a programmable data feed. The spreadsheet is one destination. JSON, SQLite, CSV and Postgres are the destinations that let Claude, a script or a dashboard ingest your money and act on it. You own the data, and you choose the shape.
Why this beats a budgeting app
A budgeting app would have categorized my transactions too, using its own categories, showing me its own views. And that would have been the end of it. I couldn’t have asked it my own questions, or built the lookup system, or pulled the data into a tool of my own.
The difference is ownership and format. When you control your transactions in a form an AI can read, the budgeting is just one thing you do. The categorization, the analysis, the tools you build next: all of it is available, because nobody put a ceiling on it. If you want the longer version of how I got here, I wrote up the whole story.
This is the manual, terminal-driven version. Next, I bring the same Claude categorization into the spreadsheet itself, as a one-click menu button you can run from Google Sheets (no terminal). After that, I have Claude build a personal finance web app that runs entirely on my own machine, against a local copy of my own transactions, so the data is fully private and fully mine. Same data, same ownership, more automation each step.
How to do this yourself
You don’t need anything fancy.
- Get your transactions out of your accounts. I use SheetLink so I can sync the card directly, including one I can only access as an authorized user. If you already export CSVs, that works too.
- Learn your rules from your own history. If you’ve already categorized transactions, that’s your training data. Build the merchant-to-category lookup from it.
- Apply the lookup, then handle the gaps. Repeats get tagged automatically. New merchants get an AI suggestion or a quick decision from you, and become rules.
- Glance at the flagged ambiguous ones. That’s the only real work left.
Questions
How do I automatically categorize bank transactions?
Use a two-layer system. First, build a lookup table that maps each merchant to your budget category, learned from transactions you have already categorized. This handles roughly 95% of merchants, which repeat every month. Second, use an AI like Claude only for the genuinely new or ambiguous merchants, where context actually requires judgment. Each AI decision becomes a new lookup rule, so the AI layer shrinks over time. Most transactions never need AI at all.
Why don’t bank categories match my budget categories?
Banks and Plaid use broad categories like FOOD_AND_DRINK or GENERAL_MERCHANDISE. Your budget uses personal categories like Groceries, Entertainment, Kids or Travel. The same generic category can map to several of your budget lines depending on context (an Amazon charge could be Kids, Household or Misc), which is why a single fixed rule doesn’t work and some transactions need judgment.
Can AI categorize my transactions without reading my whole spreadsheet?
Yes. The transaction data doesn’t have to come from your spreadsheet. With SheetLink you sync the raw transactions into Google Sheets or Excel, or pull them with the CLI as JSON or CSV (or into SQLite or Postgres with Max), then categorize them and write only the category back. The AI works from the transaction data, not your entire budget.
Do I need AI to categorize every transaction?
No, and you shouldn’t. About 95% of merchants map to one budget category every time (Netflix is always Netflix, your grocery store is always Groceries). Those are pure lookup and need no AI. Save AI for the roughly 5% of genuinely ambiguous merchants, where a human would also have to think.
What output formats does the SheetLink CLI support?
The SheetLink CLI (sheetlink sync) outputs transactions as JSON (the default, pipeable to stdout) or CSV (a flat file), and with Max as SQLite (a local, queryable database) or Postgres (through a connection string). JSON and SQLite are ideal for feeding transactions to an AI like Claude, since the data is structured and easy to query.
How do I give Claude my bank transactions to analyze?
Sync your transactions into a machine-readable format and point Claude at it. Run sheetlink sync --output json to get JSON you can pipe into a script, or, with Max, sheetlink sync --output sqlite:///path/to/finances.db to create a local database Claude can run SQL against. If you work inside Claude Code, the CLI and Claude share a terminal, so Claude can run the sync and read the output directly, without any upload or copy and paste. Claude reads the structured data and you ask questions in plain English.