Bank transactions

Imported bank transactions, read-only. Requires the transactions:read ability.

Changed: a bank transaction can now be split into parts, each booked against an invoice, an expense or a ledger account — one transfer that pays three invoices, or a payout with a provider fee in it. Three fields are new: allocations, allocation_status and unallocated_amount. Two existing fields now follow the parts: invoice_id and expense_id are filled only when exactly one part of that kind exists, and is_matched is true only when the parts cover the whole amount. An integration that read invoice_id to find "the" invoice should read allocations instead. This is a deliberate pre-launch change.

Operations

Method & path Ability
GET /api/v1/bank-transactions transactions:read
GET /api/v1/bank-transactions/{bankTransaction} transactions:read

Get a bank transaction

curl https://billey.nl/api/v1/bank-transactions/9001 \
  -H "Authorization: Bearer $TOKEN"
{
  "data": {
    "id": 9001,
    "amount": "-49.99",
    "description": "Card payment Office Supplies",
    "counterpart_name": "Office Supplies B.V.",
    "counterpart_iban": "NL00RABO0123456789",
    "end_to_end_id": "NOTPROVIDED",
    "executed_at": "2026-07-21",
    "value_date": "2026-07-21",
    "invoice_id": null,
    "expense_id": 88,
    "is_matched": true,
    "allocation_status": "allocated",
    "unallocated_amount": "0.00",
    "allocations": [
      { "id": 31, "target_type": "expense", "target_id": 88, "account_code": null, "amount": "-49.99" }
    ]
  }
}

The amount is signed — negative for money out, positive for money in (unlike invoice totals, which are always positive). end_to_end_id is the canonical SEPA reconciliation identifier.

Split transactions

allocations lists every part of the transaction. Each part has a target_type (invoice, expense or account), a target_id and an amount. An account part also has an account_code — the ledger account's code, the way ledger entries name accounts. A part's amount is signed the same way as the transaction's: in a payout of 920.00 that pays an invoice of 1000.00 and has a provider fee of 80.00 in it, the parts are 1000.00 and -80.00.

A payout split over an invoice and a fee:

{
  "data": {
    "id": 9002,
    "amount": "920.00",
    "invoice_id": 512,
    "expense_id": null,
    "is_matched": true,
    "allocation_status": "allocated",
    "unallocated_amount": "0.00",
    "allocations": [
      { "id": 40, "target_type": "invoice", "target_id": 512, "account_code": null, "amount": "1000.00" },
      { "id": 41, "target_type": "account", "target_id": 7, "account_code": "4550", "amount": "-80.00" }
    ]
  }
}
  • allocation_status is open (no parts), partial (parts that do not cover the whole amount yet) or allocated (parts that cover it exactly).
  • unallocated_amount is what the parts do not cover yet. It counts parts only: a transaction booked whole as a categorization or a privé movement has no parts, so it reports open and its full amount here — read bank_categorization and equity_type for those.
  • is_matched is true only for allocated. A vendor link alone does not match a transaction.
  • invoice_id and expense_id are filled only when exactly one part of that kind exists. A transfer that pays three invoices has invoice_id: null — the three invoices are in allocations.
  • invoice_id can be set while is_matched is false: the invoice is paid by this transaction, but part of the amount is not booked yet. Read is_matched or allocation_status for "fully booked", not invoice_id.

See the OpenAPI spec for the full field list.

Klopt er iets niet of ontbreekt er iets? support@billey.nl