View as Markdown

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

idstring

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

entity_idstring

The entity that owns the custody account. Set at creation, immutable afterwards.

namestring or null

Display name. null when the custody account has no name.

external_identifierstring or null

The 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 null

The cash account that settles the custody account's trades by default.

custodian_idstring

The bank on the day read, from custodians.

connection_typestring or null

How the custody account is fed: aggregator, custodian_feed or manual. null when not known.

managedboolean

true when 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 to false.

release_modestring or null

How the custody account's data is released: manual, auto or planned. null when its release group decides. Linked cash and custody accounts are released together, so they share one release mode.

valid_fromdate or null

The first day the custody account holds, inclusive. null when it holds from the beginning.

valid_todate or null

The last day the custody account holds, inclusive. null while it is open.

cash_account_idsarray of strings

The 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 null

The investment manager the custody account is run by.

lot_sourcestring

Where the tax lots come from on the day read: custodian, computed, or none when lots are not tracked. A new custody account starts none, and so does any custody account with no lot source set on that day; see switch lot source.

cost_basis_methodstring or null

AVG, FIFO, HIFO, LIFO or SpecificID. With lot_source none it is always AVG, the pooled average cost the legacy platform uses. With tracked lots, null = the entity's method.

period_lock_appliesboolean

true when the custody account follows the period lock: a locked month cannot change. Only Aleta sets it.

originstring

v1 when the custody account was made in v1, legacy when it comes from the legacy platform. A legacy custody account is read-only until your move to v1 is done.


GET/v1/custody-accounts

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_idstring

Only the custody accounts of this entity. A malformed id is a 400.

as_ofdate

Only 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_of shows the day read. A malformed date is a 400.

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. An invalid cursor is a 400.

Request
GET/v1/custody-accounts
curl -G https://api.aleta.ai/v1/custody-accounts \
  -H "Authorization: Bearer {access_token}" \
  -d as_of=2026-06-30
Response
{
  "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"
}

POST/v1/custody-accounts

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_idstring

The entity to create the custody account under. Required.

custodian_idstring

The bank, from custodians. Required.

connection_typestring

aggregator, custodian_feed or manual. Required.

valid_fromdate

The first day the custody account holds. Optional; left out, it holds from the beginning.

valid_todate

The last day the custody account holds, inclusive. Optional; left out, it is open.

default_cash_account_idstring

The default cash account, of the same entity. Required unless connection_type is manual.

cash_account_idsarray of strings

Further cash accounts of the entity to link. Optional.

external_identifierstring

The number at the bank. Required with custodian_feed.

namestring

Display name. Optional; not blank.

release_modestring

manual, auto or planned. Optional; left out, it is the release mode of the default cash account (or the first linked cash account), and null without one.

investment_manager_idstring

The investment manager. Optional.

managedboolean

true when Aleta is to run the custody account. Defaults to false.

Request
POST/v1/custody-accounts
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"
  }'

GET/v1/custody-accounts/{id}

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.

Request
GET/v1/custody-accounts/{id}
curl https://api.aleta.ai/v1/custody-accounts/019f8d70-9b1a-7c44-8e2f-0a6b5c4d3e21 \
  -H "Authorization: Bearer {access_token}"

PATCH/v1/custody-accounts/{id}

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:

  • 400 for a body the route cannot take: an unknown field, a blank name, or effective_from sent with none of custodian_id, cash_account_ids and default_cash_account_id; 403 for period_lock_applies from anyone but Aleta.
  • 404 when you cannot see the custody account, 403 when your grants do not cover it, 409 legacy_managed for a legacy custody account before your move to v1.
  • 404 for a cash account to link that you cannot see, 400 for a cash account of another entity or a default outside cash_account_ids.
  • The write's own refusals: 403 for managed set to false by anyone but Aleta, 400 for clearing the default when connection_type is not manual, 400 for a custodian_id that is no custodian, 409 mixed_cluster for a cash account linked anew that is released another way, 400 for a valid_to before valid_from, 409 period_locked for a day in a locked month, 403 when a new release_mode would move a linked cash or custody account your grants do not cover, and 409 duplicate_account when a new custodian_id or external_identifier makes it share a number with another custody account without an end date at the bank (next_action points to it).

Body parameters

namestring or null

New display name, or null to clear it.

external_identifierstring or null

New number at the bank (it replaces the old one), or null to clear it.

default_cash_account_idstring or null

Another 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 in cash_account_ids. null clears it, only when connection_type is manual.

custodian_idstring

The bank. It cannot be cleared. Without effective_from it 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_typestring

aggregator, custodian_feed or manual.

investment_manager_idstring or null

The investment manager, or null to clear it.

release_modestring or null

manual, auto or planned, or null to 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 (403 otherwise).

managedboolean

true hands the custody account to Aleta. Only Aleta sets it back to false; anyone else gets 403.

period_lock_appliesboolean

Aleta only; anyone else gets 403.

valid_fromdate or null

A new first day, or null for "from the beginning".

valid_todate or null

The last day the custody account holds, or null to reopen it. A day in a locked month is refused with 409 period_locked.

cash_account_idsarray of strings

The 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_fromdate

The first day a new custodian_id, cash_account_ids or default_cash_account_id holds. Sent with none of them, it is a 400.

reasonstring

Why, logged with the change.

Request
PATCH/v1/custody-accounts/{id}
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/v1/custody-accounts/{id}

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.

Request
DELETE/v1/custody-accounts/{id}
curl -X DELETE https://api.aleta.ai/v1/custody-accounts/019f8d70-9b1a-7c44-8e2f-0a6b5c4d3e21 \
  -H "Authorization: Bearer {access_token}" \
  -H "Idempotency-Key: {uuid}"

POST/v1/custody-accounts/{id}/switch-lot-source

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_sourcestring

custodian (lots from the bank), computed (lots computed with cost_basis_method) or none (lots not tracked). Required.

cost_basis_methodstring

AVG, FIFO, HIFO, LIFO or SpecificID. SpecificID only comes with the bank's lots: with computed it is a 400. Optional; left out, the current method is kept, and none stored means the entity's method.

effective_fromdate

The first day the new lot source holds. Optional; defaults to today (UTC).

Response

custody_account_idstring

The custody account switched.

lot_trackedboolean

false when lot_source was none.

lot_sourcestring

custodian, computed, or none when lots are not tracked.

cost_basis_methodstring or null

The method from effective_from: AVG when lots are not tracked; null when the entity's method applies.

previous_lot_sourcestring

The lot source before the switch; none when none held that day.

effective_fromdate

The first day the switch holds.

recalculation_statusstring

Always not_available.

Request
POST/v1/custody-accounts/{id}/switch-lot-source
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"}'
Response
{
  "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"
}
  • 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.