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_statusandunallocated_amount. Two existing fields now follow the parts:invoice_idandexpense_idare filled only when exactly one part of that kind exists, andis_matchedistrueonly when the parts cover the whole amount. An integration that readinvoice_idto find "the" invoice should readallocationsinstead. 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_statusisopen(no parts),partial(parts that do not cover the whole amount yet) orallocated(parts that cover it exactly).unallocated_amountis 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 reportsopenand its full amount here — readbank_categorizationandequity_typefor those.is_matchedistrueonly forallocated. A vendor link alone does not match a transaction.invoice_idandexpense_idare filled only when exactly one part of that kind exists. A transfer that pays three invoices hasinvoice_id: null— the three invoices are inallocations.invoice_idcan be set whileis_matchedisfalse: the invoice is paid by this transaction, but part of the amount is not booked yet. Readis_matchedorallocation_statusfor "fully booked", notinvoice_id.
See the OpenAPI spec for the full field list.