Custody accounts
Custody accounts describe where assets physically sit — the custody locations securities are held at, as opposed to the cash accounts cash moves through. Like cash accounts, a custody account belongs to an entity and can be grouped into lenses for reporting.
Reads need structure:read, writes structure:write. Every write takes ?dry_run=true and an Idempotency-Key header (see Writes), and is logged in the change list. A body field the create or the update does not know answers 400, and so does a query parameter the list does not know. Releasing a custody account and comparing its staged and published holdings are publish work, described under publish runs.
Data model
idstringStable identifier for the custody account. A UUID — treat it as opaque.
entity_idstringThe entity that owns the custody account. Set at creation, immutable afterwards.
namestring or nullDisplay name.
nullwhen the custody account has no name.external_identifierstring or nullThe custody account's number at its bank. Two custody accounts without an end date cannot share a number at the same bank, counting each one's alternative numbers.
default_cash_account_idstring or nullThe cash account that settles the custody account's trades by default.
custodian_idstringThe bank on the day read, from custodians.
connection_typestring or nullHow the custody account is fed:
aggregator,custodian_feedormanual.nullwhen not known.managedbooleantruewhen Aleta runs the custody account. Set it when you create the custody account; afterwards you can hand a custody account to Aleta, but only Aleta sets it back tofalse.release_modestring or nullHow the custody account's data is released:
manual,autoorplanned.nullwhen its release group decides. Linked cash and custody accounts are released together, so they share one release mode.valid_fromdate or nullThe first day the custody account holds, inclusive.
nullwhen it holds from the beginning.valid_todate or nullThe last day the custody account holds, inclusive.
nullwhile it is open.cash_account_idsarray of stringsThe cash accounts linked to the custody account on the day read, the default among them. Linked cash and custody accounts are released together.
investment_manager_idstring or nullThe investment manager the custody account is run by.
lot_sourcestringWhere the tax lots come from on the day read:
custodian,computed, ornonewhen lots are not tracked. A new custody account startsnone, and so does any custody account with no lot source set on that day; see switch lot source.cost_basis_methodstring or nullAVG,FIFO,HIFO,LIFOorSpecificID. Withlot_sourcenoneit is alwaysAVG, the pooled average cost the legacy platform uses. With tracked lots,null= the entity's method.period_lock_appliesbooleantruewhen the custody account follows the period lock: a locked month cannot change. Only Aleta sets it.originstringv1when the custody account was made in v1,legacywhen it comes from the legacy platform. A legacy custody account is read-only until your move to v1 is done.
List custody accounts
Returns the custody accounts visible to the authenticated credential that hold on as_of, oldest first, in the standard v1 list envelope. Deleted custody accounts are not listed.
Query parameters
entity_idstringOnly the custody accounts of this entity. A malformed id is a
400.as_ofdateOnly the custody accounts that hold on this day, with their links and settings as they stood then. Defaults to today (UTC); the answer's
as_ofshows the day read. A malformed date is a400.limitintegerPage size. Defaults to 100, capped at 1 000.
cursorstringOpaque cursor from the previous page's
next_cursor. Omit for the first page. An invalid cursor is a400.
curl -G https://api.aleta.ai/v1/custody-accounts \
-H "Authorization: Bearer {access_token}" \
-d as_of=2026-06-30
{
"data": [
{
"id": "019f8d70-9b1a-7c44-8e2f-0a6b5c4d3e21",
"entity_id": "019eb3d2-fc2e-7782-973e-af78d0c10fa6",
"name": "Main custody",
"external_identifier": "3001-123456",
"default_cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e34b",
"custodian_id": "019c0a52-1f0e-7a31-9a5b-3f1d2c7e8a10",
"connection_type": "custodian_feed",
"managed": false,
"release_mode": null,
"valid_from": "2026-01-01",
"valid_to": null,
"cash_account_ids": ["019dff7d-5ab5-7ed4-a78d-93f03070e34b"],
"investment_manager_id": null,
"lot_source": "none",
"cost_basis_method": "AVG",
"period_lock_applies": true,
"origin": "v1"
}
],
"next_cursor": null,
"limit": 100,
"as_of": "2026-06-30"
}
Create a custody account
Creates a custody account under an existing entity and links it to its cash accounts from valid_from to valid_to. It is released together with its default cash account; a manual custody account without one is placed in your v1 release group. Its lots start untracked: lot_source none with cost_basis_method AVG. Returns 201 with the created custody account.
A body the route cannot take is a 400: an unknown field, a blank name, default_cash_account_id left out when connection_type is not manual, external_identifier left out with custodian_feed, or a valid_to before valid_from. An entity or cash account you cannot see is a 404, and a cash account of another entity a 400. Your grants must then cover the entity whole with a grant that writes (403 otherwise; an auditor or lens viewer grant does not). A custodian_id that is no custodian is a 400. A linked cash account with another release_mode is a 409 mixed_cluster, whose next_action points to PATCH /v1/cash-accounts/{id} with release_mode. A custody account without an end date at the same bank with the same number, or with it among its alternative numbers, is a 409 duplicate_account, whose next_action points to PATCH /v1/custody-accounts/{id} of the one already there.
Body parameters
entity_idstringThe entity to create the custody account under. Required.
custodian_idstringThe bank, from custodians. Required.
connection_typestringaggregator,custodian_feedormanual. Required.valid_fromdateThe first day the custody account holds. Optional; left out, it holds from the beginning.
valid_todateThe last day the custody account holds, inclusive. Optional; left out, it is open.
default_cash_account_idstringThe default cash account, of the same entity. Required unless
connection_typeismanual.cash_account_idsarray of stringsFurther cash accounts of the entity to link. Optional.
external_identifierstringThe number at the bank. Required with
custodian_feed.namestringDisplay name. Optional; not blank.
release_modestringmanual,autoorplanned. Optional; left out, it is the release mode of the default cash account (or the first linked cash account), andnullwithout one.investment_manager_idstringThe investment manager. Optional.
managedbooleantruewhen Aleta is to run the custody account. Defaults tofalse.
curl -X POST https://api.aleta.ai/v1/custody-accounts \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: {uuid}" \
-d '{
"entity_id": "019eb3d2-fc2e-7782-973e-af78d0c10fa6",
"custodian_id": "019c0a52-1f0e-7a31-9a5b-3f1d2c7e8a10",
"connection_type": "custodian_feed",
"valid_from": "2026-01-01",
"default_cash_account_id": "019dff7d-5ab5-7ed4-a78d-93f03070e34b",
"external_identifier": "3001-123456",
"name": "Main custody"
}'
Retrieve a custody account
Fetch a single custody account by id, with its bank, links and lot method as of today. Returns the bare object, or a 404 problem when the id doesn't exist for this tenant, is not visible to you, or the custody account is deleted.
curl https://api.aleta.ai/v1/custody-accounts/019f8d70-9b1a-7c44-8e2f-0a6b5c4d3e21 \
-H "Authorization: Bearer {access_token}"
Update a custody account
Partially updates a custody account and returns 200 with the updated custody account. entity_id is immutable. Close it by setting valid_to: its links end on that day too. Switch where its lots come from with switch lot source.
Writing to a custody account takes a grant that covers it whole: the tenant, its client, its entity or the custody account itself. A grant on a party or a lens does not. The answers come in this order:
400for a body the route cannot take: an unknown field, a blankname, oreffective_fromsent with none ofcustodian_id,cash_account_idsanddefault_cash_account_id;403forperiod_lock_appliesfrom anyone but Aleta.404when you cannot see the custody account,403when your grants do not cover it,409 legacy_managedfor a legacy custody account before your move to v1.404for a cash account to link that you cannot see,400for a cash account of another entity or a default outsidecash_account_ids.- The write's own refusals:
403formanagedset tofalseby anyone but Aleta,400for clearing the default whenconnection_typeis notmanual,400for acustodian_idthat is no custodian,409 mixed_clusterfor a cash account linked anew that is released another way,400for avalid_tobeforevalid_from,409 period_lockedfor a day in a locked month,403when a newrelease_modewould move a linked cash or custody account your grants do not cover, and409 duplicate_accountwhen a newcustodian_idorexternal_identifiermakes it share a number with another custody account without an end date at the bank (next_actionpoints to it).
Body parameters
namestring or nullNew display name, or
nullto clear it.external_identifierstring or nullNew number at the bank (it replaces the old one), or
nullto clear it.default_cash_account_idstring or nullAnother cash account of the same entity: it becomes the default link from
effective_from(today when left out), and the old default link ends the day before, unless its cash account stays incash_account_ids.nullclears it, only whenconnection_typeismanual.custodian_idstringThe bank. It cannot be cleared. Without
effective_fromit corrects the bank the custody account has today, back to that bank's first day; with it, the bank changes from that day and the days before keep the old one. The figures from the day the bank moves are queued for a recalculation.connection_typestringaggregator,custodian_feedormanual.investment_manager_idstring or nullThe investment manager, or
nullto clear it.release_modestring or nullmanual,autoorplanned, ornullto let the release group decide. Linked cash and custody accounts are released together: the new mode moves every cash and custody account linked to this one today, each logged as its own change. Your grants must cover them all (403otherwise).managedbooleantruehands the custody account to Aleta. Only Aleta sets it back tofalse; anyone else gets403.period_lock_appliesbooleanAleta only; anyone else gets
403.valid_fromdate or nullA new first day, or
nullfor "from the beginning".valid_todate or nullThe last day the custody account holds, or
nullto reopen it. A day in a locked month is refused with409 period_locked.cash_account_idsarray of stringsThe cash accounts of the entity linked from
effective_from(today when left out), the default among them. A link to a cash account left out ends the day before; a cash account added is linked from that day.effective_fromdateThe first day a new
custodian_id,cash_account_idsordefault_cash_account_idholds. Sent with none of them, it is a400.reasonstringWhy, logged with the change.
curl -X PATCH https://api.aleta.ai/v1/custody-accounts/019f8d70-9b1a-7c44-8e2f-0a6b5c4d3e21 \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: {uuid}" \
-d '{ "valid_to": "2026-12-31" }'
Delete a custody account
Removes a custody account that nothing uses, with its links and lot periods. Returns 204 on success. The answers come in this order: 404 when the id doesn't exist for this tenant or you cannot see it, 403 when your grants do not cover it, 409 legacy_managed for a custody account from the legacy platform until your move to v1 is done.
A custody account with transactions, holdings, manual holdings, lots, custodian positions, custodian asset identifiers, invalid transactions or reconciliation breaks is not deleted: the answer is 409 in_use, naming what uses it, and next_action points to PATCH /v1/custody-accounts/{id} with valid_to, which closes it instead.
curl -X DELETE https://api.aleta.ai/v1/custody-accounts/019f8d70-9b1a-7c44-8e2f-0a6b5c4d3e21 \
-H "Authorization: Bearer {access_token}" \
-H "Idempotency-Key: {uuid}"
Switch lot source
Sets where the custody account's tax lots come from, from a date, and returns 200. The method in force ends the day before effective_from and the new one starts that day. A second switch on the same day replaces the first. Each switch is logged as a change to the custody account. recalculation_status is not_available: lots are not recomputed. An unknown field in the body is a 400.
The answers come in this order: 400 for a lot_source or cost_basis_method outside the lists below, 404 when you cannot see the custody account, 403 when your grants do not cover it with a grant that writes, 409 legacy_managed for a legacy custody account before your move to v1, 400 for an effective_from before the custody account's valid_from, 409 period_locked for a day in a locked month, 409 later_version for a switch before a later one, whose next_action points back to this route with effective_from, and 400 for computed lots with SpecificID.
Body parameters
lot_sourcestringcustodian(lots from the bank),computed(lots computed withcost_basis_method) ornone(lots not tracked). Required.cost_basis_methodstringAVG,FIFO,HIFO,LIFOorSpecificID.SpecificIDonly comes with the bank's lots: withcomputedit is a400. Optional; left out, the current method is kept, and none stored means the entity's method.effective_fromdateThe first day the new lot source holds. Optional; defaults to today (UTC).
Response
custody_account_idstringThe custody account switched.
lot_trackedbooleanfalsewhenlot_sourcewasnone.lot_sourcestringcustodian,computed, ornonewhen lots are not tracked.cost_basis_methodstring or nullThe method from
effective_from:AVGwhen lots are not tracked;nullwhen the entity's method applies.previous_lot_sourcestringThe lot source before the switch;
nonewhen none held that day.effective_fromdateThe first day the switch holds.
recalculation_statusstringAlways
not_available.
curl -X POST https://api.aleta.ai/v1/custody-accounts/019f91be-19a8-79c2-b8f9-a5440ed07dea/switch-lot-source \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: {uuid}" \
-d '{"lot_source": "computed", "cost_basis_method": "FIFO", "effective_from": "2026-10-01"}'
{
"custody_account_id": "019f91be-19a8-79c2-b8f9-a5440ed07dea",
"lot_tracked": true,
"lot_source": "computed",
"cost_basis_method": "FIFO",
"previous_lot_source": "custodian",
"effective_from": "2026-10-01",
"recalculation_status": "not_available"
}
Related
- Cash accounts — the cash side of custody.
- Lenses — how custody accounts are grouped for reporting.
- Holdings — positions can be broken down per custody account via
expand=custody_accounts. - Publish runs — releasing a custody account and its staging diff.