Publish runs
A publish run releases cash and custody accounts: their calculated holdings and performance move from staging to what you see. A custody account is always released with the cash accounts linked to it. Reads need platform:read and the operations module on read in one of your groups (an admin, Aleta, or a user in no group always has it); without it the routes answer 403.
A run is account_release (a custody or cash account released by hand with POST /v1/custody-accounts/{id}/release or POST /v1/cash-accounts/{id}/release), auto_release (released automatically after a calculation) or node_publish (the custody and cash accounts in release mode planned under a client, an entity or the tenant). There is no route to publish a node: POST /v1/publish-runs answers 404.
Until your tenant has moved from the legacy platform, the runs are the legacy release groups' publications: one run per release group and day, origin: legacy, kind: auto_release, status: succeeded, with the group's accounts released through that day. Legacy kept no run, so these have no request time of their own (requested_at is when the status was last written), no start or finish time, no count and no actor.
Data model
idstringStable identifier for the run. A UUID; for a legacy publication it is fixed per release group and day.
kindstringaccount_release,auto_releaseornode_publish.nodeobject or nullWhat was released or published:
{kind, id}withkindone ofcustody_account,cash_account,client,entityortenant(idis null for the tenant). Null when the run has no node, as for a legacy publication.statusstringqueued,running,succeededorfailed.failure_reasonstring or nullWhy a failed run failed, in plain words.
forcebooleanThe run was asked to release clusters whose checks failed. A forced run always has a
reason.reasonstring or nullThe reason given with the run.
released_throughdate or nullThe day the run releases through (YYYY-MM-DD); null when each cluster takes its newest staged day.
holdings_publishedinteger or nullHow many holdings rows the run published, once it has finished.
requested_by_user_idstring or nullThe user who asked for the run. Null when no user asked for it, as for a legacy publication.
requested_at, started_at, finished_attimestampRFC 3339, UTC, microseconds.
started_atandfinished_atare null until the run starts and finishes.originstringlegacy(a legacy release group's publication) orv1.custody_accounts, cash_accountsarray of objectThe run's accounts you can see, each
{id, outcome, held_reason, released_through}.outcomeispendinguntil the run releases the account, thenreleased; it isheldwhen the run skipped the account's cluster (an account of it in another release mode, nothing staged, or failed checks withoutforce), andheld_reasonsays why in plain words.released_throughis null for a held account.
List publish runs
Returns the runs newest first (requested_at, then id), as a cursor list. An admin sees every run; another user sees the runs with at least one custody or cash account they have access to, and only those accounts. An unknown query parameter is a 400.
Query parameters
statusstringqueued,running,succeededorfailed.kindstringaccount_release,auto_releaseornode_publish.custody_account_id, cash_account_idstringOnly runs that include this custody or cash account.
client_id, entity_idstringOnly runs with a custody or cash account under this client or entity.
from_date, to_datedateOnly runs requested on or after, and on or before, this UTC day (YYYY-MM-DD).
from_dateafterto_dateis a400.cursor, limitstring, integerThe page:
limitdefaults to 100, at most 1000. A malformed cursor is a400.
curl "https://api.aleta.ai/v1/publish-runs?status=succeeded" \
-H "Authorization: Bearer {access_token}"
{
"data": [
{
"id": "019f9a10-6c2e-7d41-9b3a-2e8f0c5d7a11",
"kind": "account_release",
"node": {
"kind": "custody_account",
"id": "019dfeb0-0b8e-7c63-9d20-5f1a2b3c4d5e"
},
"status": "succeeded",
"failure_reason": null,
"force": false,
"reason": null,
"released_through": "2026-10-16",
"holdings_published": 412,
"requested_by_user_id": "019dfe9f-77aa-7b10-8c2d-3e4f5a6b7c8d",
"requested_at": "2026-10-17T06:12:00.000000Z",
"started_at": "2026-10-17T06:12:03.120000Z",
"finished_at": "2026-10-17T06:12:09.480000Z",
"origin": "v1",
"custody_accounts": [
{
"id": "019dfeb0-0b8e-7c63-9d20-5f1a2b3c4d5e",
"outcome": "released",
"held_reason": null,
"released_through": "2026-10-16"
}
],
"cash_accounts": [
{
"id": "019dfeb0-0c41-7a2b-8e3f-6a7b8c9d0e1f",
"outcome": "released",
"held_reason": null,
"released_through": "2026-10-16"
}
]
}
],
"next_cursor": null,
"limit": 100
}
Retrieve a publish run
Returns one run with the accounts you can see. An unknown id, a run with none of your accounts, or an id that is not a UUID is a 404.
curl https://api.aleta.ai/v1/publish-runs/019f9a10-6c2e-7d41-9b3a-2e8f0c5d7a11 \
-H "Authorization: Bearer {access_token}"
{
"id": "019f9a10-6c2e-7d41-9b3a-2e8f0c5d7a11",
"kind": "account_release",
"node": {
"kind": "custody_account",
"id": "019dfeb0-0b8e-7c63-9d20-5f1a2b3c4d5e"
},
"status": "succeeded",
"failure_reason": null,
"force": false,
"reason": null,
"released_through": "2026-10-16",
"holdings_published": 412,
"requested_by_user_id": "019dfe9f-77aa-7b10-8c2d-3e4f5a6b7c8d",
"requested_at": "2026-10-17T06:12:00.000000Z",
"started_at": "2026-10-17T06:12:03.120000Z",
"finished_at": "2026-10-17T06:12:09.480000Z",
"origin": "v1",
"custody_accounts": [
{
"id": "019dfeb0-0b8e-7c63-9d20-5f1a2b3c4d5e",
"outcome": "released",
"held_reason": null,
"released_through": "2026-10-16"
}
],
"cash_accounts": [
{
"id": "019dfeb0-0c41-7a2b-8e3f-6a7b8c9d0e1f",
"outcome": "released",
"held_reason": null,
"released_through": "2026-10-16"
}
]
}
Release an account
Releases a custody account in release mode manual by hand, together with the cash accounts linked to it; POST /v1/cash-accounts/{id}/release releases a cash account's cluster the same way, with the same body and refusals. Needs platform:write, and only the firm's administrators and Aleta release (403 for anyone else). Takes ?dry_run=true and an Idempotency-Key like every write; the refusals below come on a dry run too.
Releasing is off. Nothing publishes staged holdings per account, so a release that passes every check below answers 409 release_unavailable, with next_action pointing to the account (ask Aleta), and no run is queued.
Body
A JSON body is required; send {} for the defaults. A field not listed here is a 400.
released_throughstringOptional. Release through this day (
YYYY-MM-DD, UTC, not after today); default the cluster's newest staged day.forcebooleanOptional, default
false. Release a cluster whose checks failed. Needs areason.reasonstringOptional. Why; required (not blank) with
force, kept on the run.
Refusals
| status | code | when | next_action |
|---|---|---|---|
400 | bad_request | The body: an unknown field, released_through not YYYY-MM-DD or after today, or force without a reason. | |
403 | forbidden | You lack platform:write, or you are neither an administrator of the firm nor Aleta. | |
404 | not_found | An unknown custody or cash account, or one you do not see. | |
409 | legacy_managed | Your tenant has not moved from the legacy platform: the release groups publish. | |
409 | in_use | An account of the cluster is in release mode auto or planned, or has none. | PATCH the release_mode of the custody or cash account the refusal names. |
409 | nothing_staged | The cluster has nothing staged to release. | |
409 | checks_failed | A data check of the cluster failed; the detail lists them. | The same POST with force and a reason. |
409 | release_unavailable | Every check passed, but releasing is off. | Ask Aleta (GET the custody or cash account). |
curl -X POST https://api.aleta.ai/v1/custody-accounts/019dfeb0-0b8e-7c63-9d20-5f1a2b3c4d5e/release \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: {uuid}" \
-d '{"released_through": "2026-10-16"}'
Compare staged and published
What a release of the custody account would publish: each staged holding next to the published one on the same key. Newest day first, as a cursor list. Needs platform:read and the operations module, like the runs. An unknown custody account, or one you do not see, is 404; any other query parameter, or a malformed cursor, 400.
Off. Nothing stages holdings per account, so the route answers 409 release_unavailable for a custody account you see.
Query parameters
as_ofdateOnly this staged day (
YYYY-MM-DD); echoed as the answer'sas_of.cursor, limitstring, integerThe page:
limitdefaults to 100, at most 1000.
Rows
idstringThe staged holding.
as_of_datedateThe staged day.
asset_id, holding_kind, basis, currency_codestringThe holding's key.
cash_account_idstring or nullThe cash account of a cash or fee row.
changestringadded(nothing published on the key),changed, orunchanged(quantity and market value as published).staged_quantity, staged_market_value, published_quantity, published_market_valuedecimal string or nullThe staged and the published values; the published ones are null for an
addedrow.