AI Categorisation

How TaxMTD uses Google Gemini AI to categorise transactions into HMRC-compliant expense, income and transfer categories across SA103, CT600, VAT and UC.

How It Works

TaxMTD sends uncategorised transactions to Google Gemini AI with the transaction description, amount, date, and existing merchant memory patterns. The AI returns a category with written reasoning.

Categories are universal - the same "Vehicle expenses" category is picked whether you're a sole trader, a limited company, VAT-registered, or claiming Universal Credit. Form-specific box mapping happens at filing time:

FilingWhere the mapping lives
SA103F / SA103S (sole trader)/api/hmrc/sa103 - maps category slug → SA103 box
CT600 (limited company)/api/hmrc/ct600 - maps category slug → CT600 P&L field
VAT return/api/vat/return - splits by VAT treatment, not category
Universal Credit assessmentUC engine - uses system: 'uc' or 'both' categories

That means you categorise once, and every return for every entity type reads from the same ledger. If you later switch from sole trader to limited company, your historical categorisation doesn't have to be redone.

Category Taxonomy

TaxMTD seeds a full taxonomy of expense, income, and transfer categories on first sign-up. Sub-categories are listed under each parent for reporting granularity - the AI is free to pick either a parent or a specific sub-category.

The SA103 box column below is shown as the canonical reference. For limited companies the equivalent CT600 field is in parentheses.

Expense categories

CategorySA103 Box (CT600 field)Sub-categoriesExamples
Goods and materialsBox 10 (Cost of sales)-Stock, raw materials, packaging
CIS subcontractor paymentsBox 18 (Cost of sales)-Subcontractor labour, CIS deductions
Staff expensesBox 19 (Staff costs)-Wages, employer NI, pension contributions
Vehicle expensesBox 20 (Travel costs)Fuel & charging · Vehicle insurance · Repairs & servicing · Parking & tolls · Vehicle lease/hireFuel, MOT, car insurance
Transport and travelBox 21 (Travel costs)Public transport · Taxis & ride-hailing · Flights · Accommodation · Meals & subsistenceTrain tickets, taxis, flights
Premises costsBox 22 (Premises costs)Rent · Business rates · Utilities · Premises insurance · Home office costsRent, rates, electricity
Repairs & maintenanceBox 23 (Other expenses)-Office repairs, equipment maintenance
Legal and financial costsBox 25 (Legal & professional)Accountancy fees · Legal fees · Bank charges · Business insuranceAccountant, bank charges
Marketing & advertisingBox 25 (Other expenses)-Google Ads, website hosting, flyers
Office & equipmentBox 25 (Admin expenses)Software & SaaS · Hardware & equipment · Office supplies · Phone & internetStationery, computer, software
Professional subscriptionsBox 25 (Admin expenses)-ACCA, RICS, trade journals
ClothingBox 25 (Other expenses)-Uniforms, safety boots (not everyday clothing)
Other expensesBox 25 (Other expenses)-Anything not covered above
Irrecoverable debtsBox 26 (Other expenses)-Written-off invoices
Capital allowancesBox 28 (Capital allowances)-AIA, WDA - CT600 computes separately via CT600 tax computation
Depreciation & asset disposalsBox 48 (CT600 depreciation)-Accounting depreciation. Not tax-deductible on SA103 (disallowable adjustment) but required on CT600 as a P&L line then added back in the tax computation. Logged here so management accounts balance.
National InsuranceNot deductible-Personal Class 2 & Class 4 NI is a tax, not a business expense - it's calculated on your profit, it doesn't reduce it. Employer NI for staff goes in SA103 Box 19 / CT600 Staff costs. Directors' NI goes in Staff costs for CT600 too.
TaxNot deductible-Income tax (sole trader) and Corporation Tax (limited company) aren't deductible against themselves. Tracked for UC and cashflow only.
PensionsSA100 TR4 / CT600 Staff costs-Sole trader: personal pension contributions get relief on SA100, not SA103. Limited company: employer pension contributions are deductible on CT600 as part of Staff costs. The category stays the same; the filing pipeline routes it correctly per entity type.

Income categories

CategorySA103 BoxSub-categoriesNotes
Sales & Trading IncomeBox 9 (Turnover)Services · Products · Recurring / subscriptionsMain business turnover - feeds SA103 Box 9 for sole traders and CT600 Turnover for limited companies.
Other Business IncomeBox 17 (Other operating income)-Interest received, grants, commissions. SA103 Box 17 / CT600 Other operating income.
Refunds & ReturnsOffset - no box-Treated as a negative expense on the original category, so turnover isn't inflated by refunds from suppliers. Same treatment across SA103 and CT600.
Benefits & statutory paymentsSA100 / SA102-Universal Credit, Maternity Allowance etc. are not self-employment or trading income. Never hits SA103 or CT600.
Personal Income (non-business)SA100 / SA102Employment (PAYE) · Dividends · Family gifts · Loans from family · Inheritance · Pension income · OtherPAYE salary → SA102, dividends → SA100 page TR3, gifts/loans/inheritance → not taxable. Never hits SA103 or CT600.

Transfer categories

CategoryNotes
Bank transfersNo box - a transfer between your own accounts isn't income or expense, it's the same money in a different place. Excluded from P&L, SA103 and UC totals.
The system field on each category (hmrc, uc, or both) controls which reporting surfaces it appears in. Categories marked uc still appear in your UC monthly income figure and in the transactions ledger - they just don't get auto-mapped to an SA103 or CT600 box. That keeps your tax filings clean while your Universal Credit calculation stays accurate.

Merchant Memory

When you correct a categorisation, TaxMTD remembers the mapping:

  1. You re-categorise "SPOTIFY" → Office & Equipment
  2. Pattern stored in merchant memory
  3. Next "SPOTIFY" transaction → instantly categorised
Accuracy improves from ~90% to 95-99% over time as TaxMTD learns your spending patterns.

Pre-Categorisation Rules

Before AI runs, TaxMTD applies known patterns:

  • Bank charges (type CHG) → Legal & financial
  • Known software (AWS, GitHub) → Office & equipment
  • Personal markers (Tesco, Netflix) → Marked personal
  • Income markers (PAYMENT, salary) → Marked as income
  • Transfer markers (PAYPAL PAYMENT) → Excluded transfer

API

JavaScript
// Run AI categorisation for a period
const result = await $fetch('https://dev.taxmtd.uk/api/categorise', {
  method: 'POST',
  body: { periodId: 1 }
})
console.log(`Categorised: ${result.categorised}, Skipped: ${result.skipped}`)

// Manage merchant memory
const rules = await $fetch('https://dev.taxmtd.uk/api/merchant-memory')
await $fetch('https://dev.taxmtd.uk/api/merchant-memory', {
  method: 'PUT',
  body: { id: 1, category: 'office-equipment', categoryLabel: 'Office & Equipment' }
})
Python
# Run AI categorisation
res = requests.post(
    "https://dev.taxmtd.uk/api/categorise",
    json={"periodId": 1},
    cookies=session_cookies,
)
result = res.json()["data"]
print(f"Categorised: {result['categorised']}, Skipped: {result['skipped']}")

# Get merchant memory rules
rules = requests.get(
    "https://dev.taxmtd.uk/api/merchant-memory",
    cookies=session_cookies,
).json()["data"]
PHP
$result = Http::post('https://dev.taxmtd.uk/api/categorise', ['periodId' => 1])->json();
echo "Categorised: {$result['categorised']}";

// Get merchant memory rules
$rules = Http::get('https://dev.taxmtd.uk/api/merchant-memory')->json();
Rust
let client = reqwest::Client::new();
let res = client.post("https://dev.taxmtd.uk/api/categorise")
    .json(&serde_json::json!({ "periodId": 1 }))
    .send().await?;
println!("Result: {}", res.text().await?);
cURL
# Run AI categorisation
curl -X POST https://dev.taxmtd.uk/api/categorise \
  -H "Content-Type: application/json" \
  -d '{"periodId": 1}'

# List merchant memory
curl https://dev.taxmtd.uk/api/merchant-memory