Transactions

CRUD operations for transactions - list, create (CSV import), update categories, and delete.
Transactions are also available via the Public API using API keys: GET https://dev.taxmtd.uk/api/v1/transactions. See Authentication for details.

List Transactions

Retrieve all transactions, optionally filtered by period, category, or source.

periodId
number
Filter by assessment period ID
category
string
Filter by expense category slug
source
string
Filter by source account (e.g., "NatWest", "PayPal")
excluded
boolean
Include or exclude transferred/personal transactions
JavaScript
// All transactions
const txns = await $fetch('https://dev.taxmtd.uk/api/transactions')

// Filtered by period
const filtered = await $fetch('https://dev.taxmtd.uk/api/transactions', {
  params: { periodId: 1, category: 'transport-travel' }
})

// Response shape
interface Transaction {
  id: number
  date: string
  description: string
  amount: number
  category: string | null
  categoryLabel: string | null
  aiReasoning: string | null
  isPersonal: boolean
  isExcluded: boolean
  sourceAccount: string | null
  periodId: number | null
}
Python
import requests

# All transactions
res = requests.get(
    "https://dev.taxmtd.uk/api/transactions",
    cookies=session_cookies,
)
txns = res.json()["data"]

# Filtered by period
res = requests.get(
    "https://dev.taxmtd.uk/api/transactions",
    params={"periodId": 1, "category": "transport-travel"},
    cookies=session_cookies,
)
filtered = res.json()["data"]
PHP
$transactions = Http::withCookies($session)
    ->get('https://dev.taxmtd.uk/api/transactions', [
        'periodId' => 1,
    ])->json()['data'];

foreach ($transactions as $txn) {
    echo "{$txn['date']} - {$txn['description']}: £{$txn['amount']}\n";
}
Rust
#[derive(Deserialize)]
struct Transaction {
    id: i64,
    date: String,
    description: String,
    amount: f64,
    category: Option<String>,
    source_account: Option<String>,
}

#[derive(Deserialize)]
struct ApiResponse { data: Vec<Transaction> }

let res: ApiResponse = client
    .get("https://dev.taxmtd.uk/api/transactions")
    .query(&[("periodId", "1")])
    .send().await?
    .json().await?;
cURL
# All transactions
curl https://dev.taxmtd.uk/api/transactions

# Filtered
curl "https://dev.taxmtd.uk/api/transactions?periodId=1&category=transport-travel"

Import Transactions (CSV)

Upload a bank statement CSV. TaxMTD auto-detects bank format.

JavaScript
const result = await $fetch('https://dev.taxmtd.uk/api/upload', {
  method: 'POST',
  body: {
    csv: csvContent,
    periodStart: '2026-01-30',
    periodEnd: '2026-02-27',
    periodLabel: 'Jan 2026'
  }
})
// Returns: { data: { stats: { total, income, expenses, excluded }, bankLabel } }
Python
res = requests.post(
    "https://dev.taxmtd.uk/api/upload",
    json={
        "csv": csv_content,
        "periodStart": "2026-01-30",
        "periodEnd": "2026-02-27",
        "periodLabel": "Jan 2026",
    },
    cookies=session_cookies,
)
data = res.json()["data"]
print(f"Imported {data['stats']['total']} transactions from {data['bankLabel']}")
PHP
$response = Http::withCookies($session)->post(
    'https://dev.taxmtd.uk/api/upload',
    [
        'csv' => $csvContent,
        'periodStart' => '2026-01-30',
        'periodEnd' => '2026-02-27',
        'periodLabel' => 'Jan 2026',
    ]
);

$data = $response->json()['data'];
echo "Imported {$data['stats']['total']} transactions from {$data['bankLabel']}";
Rust
let res: serde_json::Value = client
    .post("https://dev.taxmtd.uk/api/upload")
    .json(&serde_json::json!({
        "csv": csv_content,
        "periodStart": "2026-01-30",
        "periodEnd": "2026-02-27",
        "periodLabel": "Jan 2026"
    }))
    .send().await?
    .json().await?;

let stats = &res["data"]["stats"];
cURL
curl -X POST https://dev.taxmtd.uk/api/upload \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "csv": "Date,Type,Description,Value,Balance\n01/02/2026,DEB,AMAZON,-29.99,1470.01",
  "periodStart": "2026-01-30",
  "periodEnd": "2026-02-27"
}
EOF

Update Transaction

Update the category, personal flag, or exclusion status.

JavaScript
await $fetch('https://dev.taxmtd.uk/api/transactions', {
  method: 'PUT',
  body: {
    id: 42,
    category: 'office-equipment',
    categoryLabel: 'Office & Equipment',
    isPersonal: false,
    isExcluded: false
  }
})
Python
requests.put(
    "https://dev.taxmtd.uk/api/transactions",
    json={
        "id": 42,
        "category": "office-equipment",
        "categoryLabel": "Office & Equipment",
        "isPersonal": False,
        "isExcluded": False,
    },
    cookies=session_cookies,
)
PHP
Http::withCookies($session)->put(
    'https://dev.taxmtd.uk/api/transactions',
    [
        'id' => 42,
        'category' => 'office-equipment',
        'categoryLabel' => 'Office & Equipment',
    ]
);
Rust
client.put("https://dev.taxmtd.uk/api/transactions")
    .json(&serde_json::json!({
        "id": 42,
        "category": "office-equipment",
        "categoryLabel": "Office & Equipment",
        "isPersonal": false,
        "isExcluded": false
    }))
    .send().await?;
cURL
curl -X PUT https://dev.taxmtd.uk/api/transactions \
  -H "Content-Type: application/json" \
  -d '{"id":42,"category":"office-equipment","categoryLabel":"Office & Equipment"}'

Transaction Splits

Split a transaction into multiple categories (e.g., 60% business, 40% personal).

JavaScript
// Get splits for a transaction
const splits = await $fetch('https://dev.taxmtd.uk/api/transaction-splits', {
  params: { transactionId: 42 }
})

// Create splits
await $fetch('https://dev.taxmtd.uk/api/transaction-splits', {
  method: 'POST',
  body: {
    transactionId: 42,
    splits: [
      { category: 'office-equipment', categoryLabel: 'Office & Equipment', percentage: 60, amount: 17.99 },
      { category: null, categoryLabel: 'Personal', percentage: 40, amount: 12.00, isPersonal: true }
    ]
  }
})

// Delete splits
await $fetch('https://dev.taxmtd.uk/api/transaction-splits', {
  method: 'DELETE',
  body: { transactionId: 42 }
})
Python
# Get splits for a transaction
res = requests.get(
    "https://dev.taxmtd.uk/api/transaction-splits",
    params={"transactionId": 42},
    cookies=session_cookies,
)
splits = res.json()["data"]

# Create splits
requests.post(
    "https://dev.taxmtd.uk/api/transaction-splits",
    json={
        "transactionId": 42,
        "splits": [
            {"category": "office-equipment", "categoryLabel": "Office & Equipment", "percentage": 60, "amount": 17.99},
            {"category": None, "categoryLabel": "Personal", "percentage": 40, "amount": 12.00, "isPersonal": True},
        ],
    },
    cookies=session_cookies,
)

# Delete splits
requests.delete(
    "https://dev.taxmtd.uk/api/transaction-splits",
    json={"transactionId": 42},
    cookies=session_cookies,
)
PHP
// Get splits for a transaction
$splits = Http::withCookies($session)
    ->get('https://dev.taxmtd.uk/api/transaction-splits', [
        'transactionId' => 42,
    ])->json()['data'];

// Create splits
Http::withCookies($session)->post(
    'https://dev.taxmtd.uk/api/transaction-splits',
    [
        'transactionId' => 42,
        'splits' => [
            ['category' => 'office-equipment', 'categoryLabel' => 'Office & Equipment', 'percentage' => 60, 'amount' => 17.99],
            ['category' => null, 'categoryLabel' => 'Personal', 'percentage' => 40, 'amount' => 12.00, 'isPersonal' => true],
        ],
    ]
);

// Delete splits
Http::withCookies($session)->delete(
    'https://dev.taxmtd.uk/api/transaction-splits',
    ['transactionId' => 42]
);
Rust
// Get splits for a transaction
#[derive(Deserialize)]
struct Split {
    category: Option<String>,
    percentage: f64,
    amount: f64,
}

#[derive(Deserialize)]
struct SplitResponse { data: Vec<Split> }

let splits: SplitResponse = client
    .get("https://dev.taxmtd.uk/api/transaction-splits")
    .query(&[("transactionId", "42")])
    .send().await?
    .json().await?;

// Create splits
client.post("https://dev.taxmtd.uk/api/transaction-splits")
    .json(&serde_json::json!({
        "transactionId": 42,
        "splits": [
            {"category": "office-equipment", "categoryLabel": "Office & Equipment", "percentage": 60, "amount": 17.99},
            {"category": null, "categoryLabel": "Personal", "percentage": 40, "amount": 12.00, "isPersonal": true}
        ]
    }))
    .send().await?;

// Delete splits
client.delete("https://dev.taxmtd.uk/api/transaction-splits")
    .json(&serde_json::json!({ "transactionId": 42 }))
    .send().await?;
cURL
# Get splits
curl "https://dev.taxmtd.uk/api/transaction-splits?transactionId=42"

# Create splits
curl -X POST https://dev.taxmtd.uk/api/transaction-splits \
  -H "Content-Type: application/json" \
  -d '{"transactionId":42,"splits":[{"category":"office-equipment","categoryLabel":"Office","percentage":60,"amount":17.99}]}'

Find Similar Transactions

Find transactions with similar descriptions for batch categorisation.

JavaScript
const similar = await $fetch('https://dev.taxmtd.uk/api/transactions-similar', {
  params: { description: 'AMAZON' }
})
// Returns transactions matching the pattern
Python
res = requests.get(
    "https://dev.taxmtd.uk/api/transactions-similar",
    params={"description": "AMAZON"},
    cookies=session_cookies,
)
similar = res.json()["data"]
PHP
$similar = Http::withCookies($session)
    ->get('https://dev.taxmtd.uk/api/transactions-similar', [
        'description' => 'AMAZON',
    ])->json()['data'];
Rust
#[derive(Deserialize)]
struct ApiResponse { data: Vec<Transaction> }

let similar: ApiResponse = client
    .get("https://dev.taxmtd.uk/api/transactions-similar")
    .query(&[("description", "AMAZON")])
    .send().await?
    .json().await?;
cURL
curl "https://dev.taxmtd.uk/api/transactions-similar?description=AMAZON"