I Built a Tool to Auto-Categorize My Bank Statements into Google Sheets

Introduction

My bank exports statements as CAMT.053 XML files — a dry ISO 20022 format nobody outside of finance wants to think about.

Every month I'd download them, manually tag transactions, and copy everything into a spreadsheet. It was tedious enough that I finally automated it.

How it works

Once the app is running, open http://localhost:8080 in your browser. There's a simple drag-and-drop UI  drop a .zip (multiple statementsor a single .xml file and it processes immediately. No curl, no terminal.

If you prefer the command line:

curl -F "file=@statement.xml" http://localhost:8080/api/payments/upload-xml

It parses every transaction out of the ISO 20022 XML, then runs them through a two-pass categorization:

  1. Keyword matching — fast and deterministic. Rules like "albert heijn" → Groceries or "zilveren kruis" → Health Insurance cover the bulk of transactions instantly.
  2. AI fallback — anything that doesn't match a keyword gets sent to GPT-4o-mini in bulk. Optional, but useful for the long tail of one-off merchants.

The response is minimal by design:

{
  "fileCount": 1,
  "transactionCount": 42,
  "uploadedToGoogleSheets": true,
  "message": "Parsed 42 transactions from 1 files and uploaded to Google Sheets"
}

The Google Sheets side

Rather than dealing with the Sheets API and OAuth, I used a Google Apps Script web app as a lightweight write endpoint. It's a doPost function deployed publicly that receives a JSON payload and appends rows to an Expenses sheet. No credentials to manage, no SDK  just a URL.

Each row written to the sheet looks like this:

importedAt date amount currency type category details
2026-03-01 10:42 2026-02-28 49.99 EUR DBIT Groceries Albert Heijn

The Spring app POSTs to that URL after categorization is done. If the URL isn't configured, it just skips the upload and still returns the parsed transactions  useful for one-off processing without the sheet.

Running it

No Java installation required if you use Docker:

docker build -t bank2budget .
docker run -p 8080:8080 \
  -e GOOGLE_SHEETS_URL="https://script.google.com/macros/s/YOUR_ID/exec" \
  -e OPENAI_API_KEY="sk-..." \
  bank2budget

Both env vars are optional  the app degrades gracefully without them.

What's to come

The categorization rules are Dutch-centric right now (ING/ABN ecosystem keywords). Making them locale-agnostic and user-configurable via a config file would make this genuinely portable. The AI fallback partially compensates, but keyword rules are faster and cheaper for the common cases.

The other thing I'd reconsider is using Google Apps Script as the Sheets adapter. It works, but deployments are manual and version management is clunky. A proper Sheets API integration with a service account would be more reliable for anything beyond personal use.

Github

The source is on my GitHub if you want to try it, or adapt it to your own bank's export format.