View as Markdown

Transactions

Transactions are the ledger — every trade, coupon, dividend, transfer, and fee booked against a cash account. Corrections are first-class: a booked row is never edited in place. Correcting cancels the original and books a replacement, and the original, its reversal and the replacement stay retrievable through the history endpoint.

Data model

idstring

Stable identifier for the transaction. A UUID — treat it as opaque.

cash_account_idstring

The cash account the transaction is booked against. Present on every row.

custody_account_idstring or null

The custody account the transaction settles through, where relevant.

asset_idstring or null

The asset the event concerns. null on pure cash events and on fees.

transaction_typestring

What kind of event this is. One of the accepted types listed below. An opening balance (a position or cash a cash or custody account started with) is opening_balance, whatever it was booked as.

trade_dateISO 8601 date or null

When the event happened economically. null on rows that legitimately carry no trade date, such as adjustment entries.

value_dateISO 8601 date or null

When the cash leg settles.

cash_account_amountdecimal string

Cash movement in the cash account's currency. Monetary values are serialized as strings — parse with a decimal type, never a float.

currency_codestring

ISO 4217 currency of cash_account_amount.

unitsdecimal string or null

The nominal as booked: the units of a trade, the gross amount of a dividend or coupon.

unit_pricedecimal string or null

The price per unit of a trade.

commissiondecimal string or null

Commission plus counterparty fee, in the cash account currency, rounded to the currency's decimals.

taxdecimal string or null

Tax withheld.

accrued_interestdecimal string or null

Accrued interest on a bond trade.

commentstring or null

Free-text comment.

categorystring or null

The private-market category, such as Capital Call.

settlement_statusstring

settled or unsettled, as booked. Never derived from the dates, and the same whatever as_of you read at.

originstring

legacy for a transaction from the legacy platform (read-only until your move to v1 is done), v1 for one booked through this API.

is_cancellationboolean

true on a reversal: the row a correction or a delete books against the original. The list shows reversals only with include_cancelled=true.

is_cancelledboolean

true on a transaction a correction or a delete cancelled: a reversal points at it.

corrected_by_transaction_idstring or null

The correction that replaced the transaction. null when nothing corrected it, so a corrected transaction reads apart from a deleted one.

linked_transaction_idstring or null

The original a reversal or a correction belongs to. null on an original, also once it is cancelled: the original never points at its reversal.

commitment_amountdecimal string or null

The size of a commitment, or what a commitment_reduction takes off it. null on other types.

call_dateISO 8601 date or null

The date a capital_call was called.

payment_dateISO 8601 date or null

The date a capital_call is due for payment.

effect_on_unfunded_commitmentdecimal string or null

A capital_call's change to the unfunded commitment, 0 when none was booked (as on the legacy platform). null on other types.

recallable_amountdecimal string or null

The part of a distribution that can be called again, 0 when none was booked. null on other types.

Decimal strings on a transaction carry no trailing zeros.

Transaction types

transaction_type is one of the types GET /v1/transaction-types lists: the platform's types and your tenant's own. Trades: buy, sell. Cash flows: deposit, withdrawal, transfer_in, transfer_out. Income: interest, dividend, coupon, drawing. Fees: fee (a general fee), management_fee, performance_fee, custody_fee, internal_fee, fee_correction, fee_refund. Costs: commission, vat, tax. FX: fx_forward_open, fx_forward_action, fx_forward_close, fx_conversion. Financing: repo_open, repo_close, collateral_post, collateral_return. Private markets: capital_call, distribution, recall, commitment, commitment_reduction. Lifecycle: corporate_action, opening_balance, closing_balance, adjustment, other.

Each type has its own body fields (fields on the type). A field the type does not have, or a required one that is missing, answers 400. A commitment and a commitment_reduction require asset_id and commitment_amount; cash_account_amount is optional on them and 0 when left out. They are v1 transactions only and do not appear in the v2/v3 transaction lists. On a tenant that cannot store a type code, commitment_amount, call_date or payment_date yet, a type or field that needs them answers 409 not_available_yet.


GET/v1/transactions

List transactions

Returns ledger rows over a date window, filtered on trade date. from_date and to_date are both required — a bare list without them is rejected with a 400 problem, shown opposite. Narrow further with the optional id filters.

By default the list is the live ledger: a corrected or deleted transaction and its reversal are left out. include_cancelled=true shows the whole chain. Opening balances are listed, as opening_balance. The answer names the date it was read at in as_of; a booked transaction is the same whatever the date.

Rows come in the legacy platform's order: by trade date, then value date (the trade date when it has none), then the time they were booked. An unknown query parameter (such as the old account_id), an id filter that is not a UUID, an invalid cursor or a to_date before from_date answers 400. An id filter you cannot see gives an empty list. Scope transactions:read.

The list is read from the analytics store and usually follows writes within about a minute: a transaction booked, corrected, deleted or removed on the legacy platform shows (or leaves) the list as soon as its change reaches the store, without waiting for the store's 10-minute refresh. GET /v1/transactions/{id} reads the ledger itself and shows a write at once.

Query parameters

from_dateISO 8601 date

Inclusive lower bound on the trade date. Required.

to_dateISO 8601 date

Inclusive upper bound on the trade date. Required.

cash_account_idstring

Restricts results to a single cash account. Optional.

custody_account_idstring

Restricts results to a single custody account. Optional.

asset_idstring

Restricts results to events on a single asset. Optional.

include_cancelledboolean

Also the reversals and the originals they cancel. Defaults to false.

as_ofISO 8601 date

The date the answer is read at, shown in as_of. Defaults to today.

limitinteger

Page size. Defaults to 100, capped at 1 000.

cursorstring

Opaque cursor from the previous page's next_cursor. Omit for the first page.

Request
GET/v1/transactions
curl -G https://api.aleta.ai/v1/transactions \
  -H "Authorization: Bearer {access_token}" \
  -d from_date=2026-07-01 \
  -d to_date=2026-07-31 \
  -d cash_account_id=019dff7d-5ab5-7ed4-a78d-93f03070e34b
Response
{
  "data": [
    {
      "id": "019f95d1-1b02-7c3a-9b1e-4f2a8c6d0e11",
      "cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e34b",
      "custody_account_id": null,
      "asset_id": null,
      "transaction_type": "deposit",
      "trade_date": "2026-07-01",
      "value_date": "2026-07-01",
      "cash_account_amount": "250000",
      "currency_code": "DKK",
      "units": null,
      "unit_price": null,
      "commission": "0",
      "tax": "0",
      "accrued_interest": null,
      "comment": null,
      "category": null,
      "settlement_status": "settled",
      "origin": "v1",
      "is_cancellation": false,
      "is_cancelled": false,
      "corrected_by_transaction_id": null,
      "linked_transaction_id": null,
      "commitment_amount": null,
      "call_date": null,
      "payment_date": null,
      "effect_on_unfunded_commitment": null,
      "recallable_amount": null
    },
    {
      "id": "019f95d2-4c11-7a90-8e2d-6b1f0a9c3d22",
      "cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e34b",
      "custody_account_id": "019dff7e-0c41-7b2a-9f6e-2d8c4a1b7e30",
      "asset_id": "019dff80-77a3-7d15-a4c9-5e0b3f2d8a64",
      "transaction_type": "capital_call",
      "trade_date": "2026-07-15",
      "value_date": "2026-07-20",
      "cash_account_amount": "-120000",
      "currency_code": "DKK",
      "units": "120000",
      "unit_price": null,
      "commission": "0",
      "tax": "0",
      "accrued_interest": null,
      "comment": "Call 4",
      "category": "Capital Call",
      "settlement_status": "settled",
      "origin": "legacy",
      "is_cancellation": false,
      "is_cancelled": false,
      "corrected_by_transaction_id": null,
      "linked_transaction_id": null,
      "commitment_amount": null,
      "call_date": "2026-07-01",
      "payment_date": "2026-07-20",
      "effect_on_unfunded_commitment": "-120000",
      "recallable_amount": null
    }
  ],
  "next_cursor": null,
  "limit": 100,
  "as_of": "2026-07-31"
}

POST/v1/transactions

Create a transaction

Books a manual transaction. Returns 201 with the created row. Takes dry_run and Idempotency-Key. Scope transactions:write. Writing takes a grant that covers the whole cash account and custody account, not just sight of them: without one the answer is 403 (a read-only grant, such as an auditor's, covers nothing). A cash account, custody account or asset that does not exist or that you cannot see answers 404. A transaction_type that is neither a platform type nor one of your own answers 400, and so does a transaction that fails the legacy platform's validation rules (detail starts with transaction failed validation). A write dated on or before its entity's closing date, on a cash or custody account the period lock covers, answers 409 period_locked with the locked cash and custody accounts in locked and the way to unlock in next_action; until your move to v1 is done the legacy platform's monthly lock is the one that counts and answers 409 conflict.

Body parameters

cash_account_idstring

The cash account to book against. Required.

transaction_typestring

The event type, from the list above. Required.

trade_dateISO 8601 date

Economic date. Required.

value_dateISO 8601 date

Settlement date. Required.

cash_account_amountdecimal string

Cash movement in the cash account's currency. Required, except on commitment and commitment_reduction.

currency_codestring

ISO 4217 currency of the amount. Required.

custody_account_idstring

Settling custody account. Optional.

asset_idstring

Asset the event concerns. Optional, except on commitment and commitment_reduction.

commentstring

Free-form note carried on the row. Optional.

settlement_statusstring

settled or unsettled, stored as sent. Defaults to settled. Optional, on every type.

unitsdecimal string

buy and sell: the units traded. Optional.

unit_pricedecimal string

buy and sell: the price per unit. Optional.

commitment_amountdecimal string

commitment and commitment_reduction: the size, or what the reduction takes off. Required on those types.

call_dateISO 8601 date

capital_call: the call date. Optional.

payment_dateISO 8601 date

capital_call: the payment date. Optional.

effect_on_unfunded_commitmentdecimal string

capital_call: its change to the unfunded commitment. Optional.

recallable_amountdecimal string

distribution: the part that can be called again. Optional.

affects_navboolean

capital_call, distribution and recall: false keeps the row out of the NAV. Also affects_net_irr, affects_multiples_gross, affects_multiples_net and affects_cost_base. Optional.

A field the body does not know, such as the old depository_id, answers 400.

Request
POST/v1/transactions
curl -X POST https://api.aleta.ai/v1/transactions \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e34b",
    "transaction_type": "deposit",
    "trade_date": "2026-07-01",
    "value_date": "2026-07-01",
    "cash_account_amount": "250000",
    "currency_code": "DKK",
    "comment": "Initial funding"
  }'
Response
{
  "id": "019f95d1-1b02-7c3a-9b1e-4f2a8c6d0e11",
  "cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e34b",
  "custody_account_id": null,
  "asset_id": null,
  "transaction_type": "deposit",
  "trade_date": "2026-07-01",
  "value_date": "2026-07-01",
  "cash_account_amount": "250000",
  "currency_code": "DKK",
  "units": null,
  "unit_price": null,
  "commission": "0",
  "tax": "0",
  "accrued_interest": null,
  "comment": "Initial funding",
  "category": null,
  "settlement_status": "settled",
  "origin": "v1",
  "is_cancellation": false,
  "is_cancelled": false,
  "corrected_by_transaction_id": null,
  "linked_transaction_id": null,
  "commitment_amount": null,
  "call_date": null,
  "payment_date": null,
  "effect_on_unfunded_commitment": null,
  "recallable_amount": null
}

GET/v1/transactions/{id}

Retrieve a transaction

Fetch a single ledger row by id: every row the list shows with include_cancelled=true, so also a cancelled original (with is_cancelled and corrected_by_transaction_id) and a reversal. Returns the bare object, or a 404 problem when the id is not a UUID, doesn't exist for this tenant or is not visible to you. Scope transactions:read.

Request
GET/v1/transactions/{id}
curl https://api.aleta.ai/v1/transactions/019f95d1-1b02-7c3a-9b1e-4f2a8c6d0e11 \
  -H "Authorization: Bearer {access_token}"
Response
{
  "id": "019f95d1-1b02-7c3a-9b1e-4f2a8c6d0e11",
  "cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e34b",
  "custody_account_id": null,
  "asset_id": null,
  "transaction_type": "deposit",
  "trade_date": "2026-07-01",
  "value_date": "2026-07-01",
  "cash_account_amount": "250000",
  "currency_code": "DKK",
  "units": null,
  "unit_price": null,
  "commission": "0",
  "tax": "0",
  "accrued_interest": null,
  "comment": null,
  "category": null,
  "settlement_status": "settled",
  "origin": "v1",
  "is_cancellation": false,
  "is_cancelled": false,
  "corrected_by_transaction_id": null,
  "linked_transaction_id": null,
  "commitment_amount": null,
  "call_date": null,
  "payment_date": null,
  "effect_on_unfunded_commitment": null,
  "recallable_amount": null
}

PATCH/v1/transactions/{id}

Correct a transaction

Corrects a booked transaction. This is not an in-place edit: the platform cancels the original row and books a replacement that carries your changes merged over the original's values. The response is the replacement row — its id is new; use it for further reads. The full chain remains available via history.

All body fields are optional; any field you omit keeps the original's value. The fields are the same as on create: cash_account_id, custody_account_id, asset_id, transaction_type, trade_date, value_date, cash_account_amount, currency_code, comment, plus an optional reason for the change log. What the body cannot carry is kept from the original when the type stays the same: units, price, costs, private-market fields and flags, the contract and fee it belongs to, the other leg of an FX trade, whether it counts in holdings and its transaction and custodian currencies (while the asset and the cash and custody accounts stay the original's). A correction of an opening balance is an opening balance too, and sending opening_balance as transaction_type on one keeps its type. The replacement passes the same checks as a create: the type's fields, the references and the validation rules.

The reversal, the replacement and the change log entry are written in one database transaction, so a failed request leaves the ledger as it was. Takes dry_run and Idempotency-Key. Scope transactions:write.

An unknown or cancelled id answers 404. 403 without a grant covering the whole cash and custody account, the original's, the new ones and those of the other leg of a transfer. After that check, a transaction that comes from the legacy platform answers 409 legacy_managed until your move to v1 is done, and so does a transfer leg whose other leg does. The correction keeps the original's settlement_status. On one leg of a transfer the link moves to the correction, so the transfer stays linked; when the correction breaks the transfer's link rules (another account, type or amount), the answer is 409 in_use with next_action naming POST /v1/transfers/{id}/unlink, and nothing is written. A field the body does not know answers 400. The lock is checked on the original's date and on the replacement's. A write dated on or before its entity's closing date, on a cash or custody account the period lock covers, answers 409 period_locked with the locked accounts and the way to unlock; until your move to v1 is done the legacy platform's monthly lock is the one that counts and answers 409 conflict.

Request
PATCH/v1/transactions/{id}
curl -X PATCH https://api.aleta.ai/v1/transactions/019f95d1-1b02-7c3a-9b1e-4f2a8c6d0e11 \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{ "cash_account_amount": "255000" }'
Response
{
  "id": "019f95d4-8e20-7f16-b7aa-1c9d3e5b2a07",
  "cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e34b",
  "custody_account_id": null,
  "asset_id": null,
  "transaction_type": "deposit",
  "trade_date": "2026-07-01",
  "value_date": "2026-07-01",
  "cash_account_amount": "255000",
  "currency_code": "DKK",
  "units": null,
  "unit_price": null,
  "commission": "0",
  "tax": "0",
  "accrued_interest": null,
  "comment": null,
  "category": null,
  "settlement_status": "settled",
  "origin": "v1",
  "is_cancellation": false,
  "is_cancelled": false,
  "corrected_by_transaction_id": null,
  "linked_transaction_id": "019f95d1-1b02-7c3a-9b1e-4f2a8c6d0e11",
  "commitment_amount": null,
  "call_date": null,
  "payment_date": null,
  "effect_on_unfunded_commitment": null,
  "recallable_amount": null
}

DELETE/v1/transactions/{id}

Delete a transaction

Cancels a transaction; nothing is deleted. The platform books a reversal that points at the original, unlinks the other leg of a transfer and logs the change, in one database transaction, and answers 204. As on the legacy platform, the rows that belong to the transaction go with it: the other leg of an FX trade, and the loan repayment of a loan's contract on the loan's trade date when there is exactly one. Each of them gets its own reversal and change, and passes the same checks as the transaction. A reversal's comment names the transaction it cancels by id (Cancellation of transaction {id}), and the change log entry of each cancelled row names its reversal in cancelled_by. The original and its reversal stay in the ledger and show with include_cancelled=true and in the history.

Pass ?reason= for the change log; any other query parameter than reason and dry_run answers 400. A 404 problem when the id doesn't exist for this tenant or is cancelled already. 403 without a grant covering the whole cash and custody account of every row the delete touches. A transaction that comes from the legacy platform answers 409 legacy_managed until your move to v1 is done. Takes dry_run and Idempotency-Key. Scope transactions:write. The lock is checked on the transaction, on the transfer leg it unlinks and on the rows that go with it, and a legacy transfer leg or related row answers 409 legacy_managed the same way. A write dated on or before its entity's closing date, on a cash or custody account the period lock covers, answers 409 period_locked with the locked accounts and the way to unlock; until your move to v1 is done the legacy platform's monthly lock is the one that counts and answers 409 conflict.

Request
DELETE/v1/transactions/{id}
curl -X DELETE https://api.aleta.ai/v1/transactions/019f95d4-8e20-7f16-b7aa-1c9d3e5b2a07 \
  -H "Authorization: Bearer {access_token}"

GET/v1/transactions/{id}/history

Retrieve correction history

Returns the chain of a transaction as a cursor list, oldest first: the original, then the reversal and the correction booked against it. Pass the id of any row in the chain (the original, its reversal or its correction) and you get the same chain back. A correction that is itself corrected later is the original of a chain of its own: pass its id to see that one. Each row is the transaction as GET /v1/transactions/{id} shows it, with:

kindstring

original, cancellation or correction.

created_atRFC 3339 timestamp

When the row was booked, UTC.

created_bystring or null

The user who booked the row through this API. null on a legacy row, and when Aleta booked it (never who at Aleta).

reasonstring or null

On a reversal and a correction: the reason the correction or delete of the chain's original was made with. null on the original, and when no reason was given.

Takes limit (100 by default, 1 000 at most) and cursor; any other query parameter, or an invalid cursor, answers 400. An unknown id answers 404. Scope transactions:read. previous_versions is on every page and lists the versions the legacy platform replaced when the transaction was edited there, oldest first: changed_at (RFC 3339, UTC, milliseconds) and the values before the edit (transaction_type, trade_date, value_date, amount (the cash_account_amount then), currency_code, units, comment). It is filled for roles that see the audit trail (tenant owners and admins) and empty for everyone else.

Request
GET/v1/transactions/{id}/history
curl https://api.aleta.ai/v1/transactions/019f95d4-8e20-7f16-b7aa-1c9d3e5b2a07/history \
  -H "Authorization: Bearer {access_token}"
Response
{
  "data": [
    {
      "id": "019f95d1-1b02-7c3a-9b1e-4f2a8c6d0e11",
      "cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e34b",
      "custody_account_id": null,
      "asset_id": null,
      "transaction_type": "deposit",
      "trade_date": "2026-07-01",
      "value_date": "2026-07-01",
      "cash_account_amount": "250000",
      "currency_code": "DKK",
      "units": null,
      "unit_price": null,
      "commission": "0",
      "tax": "0",
      "accrued_interest": null,
      "comment": null,
      "category": null,
      "settlement_status": "settled",
      "origin": "v1",
      "is_cancellation": false,
      "is_cancelled": true,
      "corrected_by_transaction_id": "019f95d4-8e20-7f16-b7aa-1c9d3e5b2a07",
      "linked_transaction_id": null,
      "commitment_amount": null,
      "call_date": null,
      "payment_date": null,
      "effect_on_unfunded_commitment": null,
      "recallable_amount": null,
      "kind": "original",
      "created_at": "2026-07-01T09:12:44.104882Z",
      "created_by": "019d4c1a-6f2e-7b10-9c3d-2a1b0c9d8e7f",
      "reason": null
    },
    {
      "id": "019f95d4-8c11-7b02-a3d5-6e0f1a2b3c4d",
      "cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e34b",
      "custody_account_id": null,
      "asset_id": null,
      "transaction_type": "deposit",
      "trade_date": "2026-07-01",
      "value_date": "2026-07-01",
      "cash_account_amount": "-250000",
      "currency_code": "DKK",
      "units": null,
      "unit_price": null,
      "commission": "0",
      "tax": "0",
      "accrued_interest": null,
      "comment": "Cancellation of transaction 019f95d1-1b02-7c3a-9b1e-4f2a8c6d0e11",
      "category": null,
      "settlement_status": "settled",
      "origin": "v1",
      "is_cancellation": true,
      "is_cancelled": false,
      "corrected_by_transaction_id": null,
      "linked_transaction_id": "019f95d1-1b02-7c3a-9b1e-4f2a8c6d0e11",
      "commitment_amount": null,
      "call_date": null,
      "payment_date": null,
      "effect_on_unfunded_commitment": null,
      "recallable_amount": null,
      "kind": "cancellation",
      "created_at": "2026-07-03T14:02:10.551203Z",
      "created_by": "019d4c1a-6f2e-7b10-9c3d-2a1b0c9d8e7f",
      "reason": "amount typo"
    },
    {
      "id": "019f95d4-8e20-7f16-b7aa-1c9d3e5b2a07",
      "cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e34b",
      "custody_account_id": null,
      "asset_id": null,
      "transaction_type": "deposit",
      "trade_date": "2026-07-01",
      "value_date": "2026-07-01",
      "cash_account_amount": "255000",
      "currency_code": "DKK",
      "units": null,
      "unit_price": null,
      "commission": "0",
      "tax": "0",
      "accrued_interest": null,
      "comment": null,
      "category": null,
      "settlement_status": "settled",
      "origin": "v1",
      "is_cancellation": false,
      "is_cancelled": false,
      "corrected_by_transaction_id": null,
      "linked_transaction_id": "019f95d1-1b02-7c3a-9b1e-4f2a8c6d0e11",
      "commitment_amount": null,
      "call_date": null,
      "payment_date": null,
      "effect_on_unfunded_commitment": null,
      "recallable_amount": null,
      "kind": "correction",
      "created_at": "2026-07-03T14:02:10.551203Z",
      "created_by": "019d4c1a-6f2e-7b10-9c3d-2a1b0c9d8e7f",
      "reason": "amount typo"
    }
  ],
  "next_cursor": null,
  "limit": 100,
  "previous_versions": []
}
POST/v1/transfers

Create a transfer

A cash transfer is two transactions on two cash accounts of the same entity, linked as one transfer: a withdrawal on the sending account and a deposit on the receiving one. For cash, the body has one of two forms. Create books both legs and links them in one database transaction: from_cash_account_id, to_cash_account_id, amount (positive, in currency_code), currency_code, trade_date, value_date and an optional comment. Link pairs two transactions that are booked already: outgoing_transaction_id and incoming_transaction_id. Sending any field of both forms, only one of the two ids, or a field neither form has answers 400; so does a missing field of the create form, an amount that is not above zero, or the same cash account twice. A cash account or transaction that does not exist or that you cannot see answers 404. Each booked leg passes the same checks as POST /v1/transactions.

The legacy platform's link rules apply to both forms: two different cash accounts of the same entity in the same currency, both legs a deposit, withdrawal, transfer_in or transfer_out and not a reversal, the same transaction currency, amounts that offset each other within 0.005, and trade dates at most 5 days apart. A broken rule answers 400; a leg that is linked already answers 409 in_use with next_action naming POST /v1/transfers/{id}/unlink for that leg, and a cancelled leg 409 conflict, as does a leg another request linked or cancelled in between.

A paper transfer moves units of one asset between two custody accounts of the same entity, as a pair linked like a cash transfer: a transfer_out on the sending custody account and a transfer_in on the receiving one, each booked on its custody account's default cash account with a cash amount of 0. Its form is from_custody_account_id, to_custody_account_id, asset_id, units (positive: the units moved), trade_date, value_date and an optional comment. A field of the cash form in it (from_cash_account_id, to_cash_account_id, amount, currency_code) or of the link form answers 400, and so do a missing field, units that are not above zero, the same custody account twice, two custody accounts of different entities and a custody account without a default cash account. A custody account that does not exist or that you cannot see answers 404, as do an asset_id that is not a UUID and an asset you cannot see. The outgoing leg stores the units as negative, the incoming as positive. Each leg passes the legacy platform's validation rules for a correction, with the asset and the custody account required (400, detail starting with transaction failed validation, otherwise). Both legs are booked at the sending custody account's average cost per unit for the asset, read from the platform's holdings figures for its portfolio on the newest day before trade_date that has them, so the cost moves with the units and the sending side realises no gain. With no cost there, the answer is 400: a paper transfer is never booked at zero cost. The cost is taken when the transfer is booked; a later change to the sender's earlier transactions does not reprice it. Both legs book in one database transaction, so a refused leg books nothing. 403 without a grant covering both custody accounts and their default cash accounts whole. Paper transfer legs are not shown by the v2 and v3 transaction reads.

Returns 201 with the transfer. Takes dry_run and Idempotency-Key. Scope transactions:write. Booking two new transactions logs each of them as a created transaction; linking two existing ones logs each leg, its pairing (counterparty_leg_id and the legacy counterparty_transaction_id) before and after. 403 without a grant covering both cash accounts whole: a grant on the tenant, the client, the entity or the cash account (not a read-only one), or Aleta's own access. After the 403 check, a leg that comes from the legacy platform answers 409 legacy_managed until your move to v1 is done. A leg dated on or before its entity's closing date answers 409 period_locked; until your move to v1 is done the legacy platform's monthly lock answers 409 conflict. 503 service_unavailable while your tenant cannot store transfers.

The transfer's amounts and a paper transfer's units are decimal strings without trailing zeros ("5000", "250.5", "4").

Request
POST/v1/transfers
curl -X POST https://api.aleta.ai/v1/transfers \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "from_cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e34b",
    "to_cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e35c",
    "amount": "5000",
    "currency_code": "DKK",
    "trade_date": "2026-07-01",
    "value_date": "2026-07-01"
  }'
Response
{
  "id": "019f95e0-3a41-7c2b-8d5e-1f6a9b0c4d21",
  "status": "linked",
  "origin": "v1",
  "from_cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e34b",
  "to_cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e35c",
  "currency_code": "DKK",
  "amount": "5000",
  "from_custody_account_id": null,
  "to_custody_account_id": null,
  "asset_id": null,
  "units": null,
  "outgoing": {
    "transaction_id": "019f95e0-3a41-7c2b-8d5e-1f6a9b0c4d21",
    "cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e34b",
    "transaction_type": "withdrawal",
    "cash_account_amount": "-5000",
    "currency_code": "DKK",
    "trade_date": "2026-07-01",
    "status": "active",
    "custody_account_id": null,
    "units": null
  },
  "incoming": {
    "transaction_id": "019f95e0-3a42-7e10-9a3c-5b8d2e7f1a64",
    "cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e35c",
    "transaction_type": "deposit",
    "cash_account_amount": "5000",
    "currency_code": "DKK",
    "trade_date": "2026-07-01",
    "status": "active",
    "custody_account_id": null,
    "units": null
  }
}

GET/v1/transfers/{id}

Retrieve a transfer

Returns a cash transfer: the outgoing and incoming legs (outgoing, incoming, each with its transaction_id, cash_account_id, transaction_type, cash_account_amount, currency_code (the cash account's currency), trade_date and status, active or cancelled) and their link. The id is the outgoing leg's transaction id (the leg with the negative amount); the incoming leg's id finds the same transfer. amount and currency_code on the transfer are the incoming leg's, the amount received. A paper transfer also has from_custody_account_id, to_custody_account_id, asset_id and units (the units received), and each leg its custody_account_id and units (signed); on a cash transfer these are null. status is linked, or cancelled when a leg of a pair that still points both ways was cancelled; a delete of a leg unlinks the other one, after which the transfer answers 404. origin is v1 for a pair linked through the API and legacy for one the legacy platform paired. A 404 when the id is not one leg of a linked pair, or either leg is not visible to you. 503 service_unavailable while your tenant cannot store transfers. Scope transactions:read. When a leg is corrected with PATCH /v1/transactions/{id}, the link moves to the correction, so the transfer stays linked under the correction's id.


POST/v1/transfers/{id}/unlink

Removes the pairing; both transactions stay as they are. Returns 200 with the transfer, status unlinked. An optional JSON body {"reason": "..."} says why; an unknown field in it is a 400. Takes dry_run and Idempotency-Key, scope transactions:write, and the change is logged once per leg (its pairing before and after, with the reason); a repeat under the same key and body replays the answer. 404 when the transfer does not exist or is unlinked already, 403 without a grant covering both cash accounts whole, 409 legacy_managed for a legacy pair until your move to v1 is done, 409 period_locked when a leg is dated on or before its entity's closing date (until your move to v1 is done the legacy platform's monthly lock is the one that counts and answers 409 conflict), and 409 conflict when another request changed the pair in between.


GET/v1/transaction-types

List transaction types

The platform's types, in the order above, then your tenant's own by type. One code names one type: a type of your own whose code a platform type has taken since is not listed, and its transactions are calculated as the platform type. One list without a cursor. Scope transactions:read.

The type

typestring

The code POST /v1/transactions takes as transaction_type.

namestring

Display name.

categorystring

One of trade, cash, income, fee, cost, fx, financing, private_markets, corporate_action, balance, other.

behaviourstring

The platform type it is calculated as. A base type is its own; management_fee, performance_fee, custody_fee, internal_fee and fee_correction are a fee, fee_refund is an adjustment (money given back), and your own type is the base type you chose.

ownerstring

aleta for a platform type, tenant for your own.

originstring

v1.

fieldsarray

The body fields of the type, {name, required}: its behaviour's.

Response
{
  "data": [
    {
      "type": "commitment",
      "name": "Commitment",
      "category": "private_markets",
      "behaviour": "commitment",
      "owner": "aleta",
      "origin": "v1",
      "fields": [
        { "name": "cash_account_id", "required": true },
        { "name": "custody_account_id", "required": false },
        { "name": "asset_id", "required": true },
        { "name": "transaction_type", "required": true },
        { "name": "trade_date", "required": true },
        { "name": "value_date", "required": true },
        { "name": "cash_account_amount", "required": false },
        { "name": "currency_code", "required": true },
        { "name": "comment", "required": false },
        { "name": "commitment_amount", "required": true }
      ]
    }
  ],
  "next_cursor": null,
  "limit": 38
}
GET/v1/transaction-types/{type}

Retrieve a transaction type

One type by its code, a platform type or your own, in the shape the list shows. An unknown code answers 404, and so does call. Scope transactions:read.

POST/v1/transaction-types

Create a transaction type

Adds your tenant's own type, calculated as a platform base type. Admins and Aleta only (403 otherwise, also for Aleta support), scope transactions:write. Returns 201 with the type. Takes dry_run and Idempotency-Key, and the change is logged. A type that is a platform type or exists already (also an archived one) answers 409 already_exists; a field the body does not know answers 400. While your tenant cannot have its own types the answer is 409 conflict. There is no update or delete.

Body parameters

typestring

snake_case code: a lower-case letter, then lower-case letters, digits or _, at most 64 characters. Required. call, the stored form of capital_call, is a 400, and so is a code with a word v1 no longer uses, such as account or portfolio.

namestring

Display name, 1 to 200 characters. Required.

behaviourstring

A platform base type, one whose behaviour is its own type, such as dividend or capital_call (not management_fee or fee_refund). Required.

categorystring

One of the categories above. Optional: the behaviour's when omitted.

A transaction of your type is stored as its behaviour, so it is calculated as that type, and it shows your code as transaction_type.

Request
POST/v1/transaction-types
curl -X POST https://api.aleta.ai/v1/transaction-types \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "rental_income",
    "name": "Rental income",
    "behaviour": "dividend"
  }'
Response
{
  "type": "rental_income",
  "name": "Rental income",
  "category": "income",
  "behaviour": "dividend",
  "owner": "tenant",
  "origin": "v1",
  "fields": [
    { "name": "cash_account_id", "required": true },
    { "name": "custody_account_id", "required": false },
    { "name": "asset_id", "required": false },
    { "name": "transaction_type", "required": true },
    { "name": "trade_date", "required": true },
    { "name": "value_date", "required": true },
    { "name": "cash_account_amount", "required": true },
    { "name": "currency_code", "required": true },
    { "name": "comment", "required": false }
  ]
}
  • Cash accounts — where transactions are booked.
  • Holdings — the positions transactions build up to.
  • Assets — what security rows point at.