Building a VAT Return System with Claude Code from Your Invoices and Expenses Database
How I went from a Postgres invoices table and a folder of supplier PDFs to HMRC Box 1 to 9 numbers, with deterministic maths, an audit trail, and a human approval gate
A short, skim-readable companion. For the full ~5,000-word deep dive with code, prompts, schema, an HMRC MTD walkthrough, and edge cases, read the long article.
Quarterly VAT was the worst part of my month
Every quarter, a UK small business has to produce a VAT return: nine boxes of numbers (sales, purchases, VAT due, VAT reclaimable, EU goods if you are in Northern Ireland, totals, net) and submit them through HMRC Making Tax Digital (MTD). For years, mine looked like this: open the invoices table in Postgres, export a CSV, paste it into Excel, hand-classify a few hundred rows, run formulas, copy nine numbers into a third-party MTD bridge, click Submit, hope.
It worked, but it was slow and lossy. Receipts went missing. Mid-quarter rate changes broke the formulas. A typo in one VAT classification could move Box 5 by hundreds of pounds. And the audit trail was "trust me, I had a spreadsheet".
This year I rebuilt the whole thing as a small Claude Code project that reads my invoices + expenses DB, classifies each line, computes the nine boxes, dry-runs a PDF, and only then submits to HMRC MTD - with a human approval gate. This blog is the short version. The long article has the prompts, the Python, the HMRC OAuth flow, and the gotchas.
Watch the walkthrough
A short walkthrough of the VAT return pipeline running end-to-end against a real (anonymised) invoices + expenses DB.
The five-step pipeline
- Connect. Claude Code gets a read-only DB connection to my invoices and expenses tables. It never has write access to the source data. CSVs from Stripe, Wise, Revolut, and a couple of bank feeds are dropped into
./imports/. - Normalise. Claude generates and runs a small Python script that maps every source schema into one canonical view:
id, date, supplier_or_customer, gross, net, vat_amount, vat_rate, vat_treatment, country_code, currency, source, run_key. Foreign currency is converted to GBP using the HMRC monthly rate. The whole period is keyed by an idempotentrun_keyso re-running is safe. - Categorise + reconcile. Claude classifies each line into a VAT treatment - standard 20%, reduced 5%, zero, exempt, outside-scope, reverse-charge, EU goods, EU services, postponed import VAT - using a small
vat-rules.mdI keep in the repo. It also reconciles invoices against payments and flags anomalies (negative net amounts, missing supplier VAT numbers, country-code mismatches, suppliers who are not VAT-registered). - Compute. A pure-function Python module - no LLM in the maths - reads the classified rows and produces Box 1 through Box 9, plus a dry-run report in Markdown. The flat-rate scheme, cash accounting, and annual accounting branches are explicit code paths, not prompt instructions.
- Submit. Only after I (or my accountant) has signed off the dry-run does the pipeline call the HMRC MTD API. OAuth2 token. Fraud-prevention headers.
GET obligationsto find the open period.POST returnswithfinalised: true. ThenGET liabilitiesto confirm what I owe and when.
Why Claude Code instead of Xero / QuickBooks / FreeAgent
Honestly, for most small businesses, a packaged accountant SaaS is the right answer. They have HMRC certification, support teams, and your accountant already knows them. So why did I do this?
- I already use Claude Code every day for software work. The marginal cost of also using it to assemble a VAT pipeline is small.
- My data is local. The invoices DB never leaves my machine. The classifier runs over rows; the maths is local Python; only the final nine numbers go to HMRC. No SaaS tenant, no third party.
- I wanted full control of the categorisation rules. Some of my supplier mix is awkward (EU services, postponed import VAT, a couple of zero-rated edge cases). Encoding those rules in
vat-rules.md+ a Python module is faster for me than configuring a SaaS. - The audit trail is mine. Every Claude prompt and every diff is committed into an append-only log alongside the signed period snapshot. If HMRC ever asks, I can produce the exact computation in seconds.
If you do not already use Claude Code or you would not enjoy reading Python at midnight, please use Xero / QuickBooks / FreeAgent. This is not a recommendation to roll your own VAT system. It is a story of what is possible when you do.
Where the LLM is and isn't
This is the part I want to be loud about. Claude is not in the VAT arithmetic. Claude classifies lines, suggests reconciliations, and generates and edits the deterministic Python modules that do the maths. The actual computation of Box 1 through Box 9 is a pure function with unit tests. The HMRC submission requires a human approval gate. The dry-run report is the source of truth for sign-off. If I cannot explain why a number is what it is from looking at the code and the classified rows, I do not submit.
This is a hard rule because finance is regulated, and "the AI did it" is not a defence under MTD. The long article goes into more detail about how to draw this line in your own implementation.
What it cost me to build, and what it costs me to run
Build time was about a week of focused evenings - mostly spent on schema normalisation, the classifier rules, and writing tests for the engine. The HMRC MTD OAuth dance was the slowest part; running against the sandbox first was worth every minute.
To run, each quarter costs me roughly: my Claude Code subscription (already paid for other work) plus a few pennies in token usage for the classifier. The HMRC API itself is free. The VAT engine and the audit log run locally on my Mac.
Important disclaimer
I am an engineer, not a chartered accountant. This article is not tax advice. Before you run anything like this against your real HMRC account, get a UK-registered accountant to review your first return end-to-end. Use the HMRC MTD sandbox until you and your accountant are both happy. The cost of a bad VAT submission is much higher than the cost of an hour of an accountant's time.
Read the long version
If you want the full picture - the exact CREATE TABLE schema, the Claude Code prompts, the deterministic Python engine with unit tests, the HMRC MTD OAuth flow, fraud-prevention headers, and a thorough section on edge cases (Brexit / NI protocol, reverse charge, partial exemption, flat-rate scheme, mid-quarter rate changes, multi-currency invoices, credit notes, deposits) - read the long article. It is the version I would have wanted to read before I started.
Watch the YouTube walkthrough, subscribe for follow-up tutorials (auto-import bank feeds, OCR for receipts, scheduled cron + Slack approval), and remember: not tax advice, accountant sign-off required.