View as Markdown

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

idstring

Stable identifier for the run. A UUID; for a legacy publication it is fixed per release group and day.

kindstring

account_release, auto_release or node_publish.

nodeobject or null

What was released or published: {kind, id} with kind one of custody_account, cash_account, client, entity or tenant (id is null for the tenant). Null when the run has no node, as for a legacy publication.

statusstring

queued, running, succeeded or failed.

failure_reasonstring or null

Why a failed run failed, in plain words.

forceboolean

The run was asked to release clusters whose checks failed. A forced run always has a reason.

reasonstring or null

The reason given with the run.

released_throughdate or null

The day the run releases through (YYYY-MM-DD); null when each cluster takes its newest staged day.

holdings_publishedinteger or null

How many holdings rows the run published, once it has finished.

requested_by_user_idstring or null

The user who asked for the run. Null when no user asked for it, as for a legacy publication.

requested_at, started_at, finished_attimestamp

RFC 3339, UTC, microseconds. started_at and finished_at are null until the run starts and finishes.

originstring

legacy (a legacy release group's publication) or v1.

custody_accounts, cash_accountsarray of object

The run's accounts you can see, each {id, outcome, held_reason, released_through}. outcome is pending until the run releases the account, then released; it is held when the run skipped the account's cluster (an account of it in another release mode, nothing staged, or failed checks without force), and held_reason says why in plain words. released_through is null for a held account.


GET/v1/publish-runs

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

statusstring

queued, running, succeeded or failed.

kindstring

account_release, auto_release or node_publish.

custody_account_id, cash_account_idstring

Only runs that include this custody or cash account.

client_id, entity_idstring

Only runs with a custody or cash account under this client or entity.

from_date, to_datedate

Only runs requested on or after, and on or before, this UTC day (YYYY-MM-DD). from_date after to_date is a 400.

cursor, limitstring, integer

The page: limit defaults to 100, at most 1000. A malformed cursor is a 400.

Request
GET/v1/publish-runs
curl "https://api.aleta.ai/v1/publish-runs?status=succeeded" \
  -H "Authorization: Bearer {access_token}"
Response
{
  "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
}

GET/v1/publish-runs/{id}

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.

Request
GET/v1/publish-runs/{id}
curl https://api.aleta.ai/v1/publish-runs/019f9a10-6c2e-7d41-9b3a-2e8f0c5d7a11 \
  -H "Authorization: Bearer {access_token}"
Response
{
  "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"
    }
  ]
}

POST/v1/custody-accounts/{id}/release

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_throughstring

Optional. Release through this day (YYYY-MM-DD, UTC, not after today); default the cluster's newest staged day.

forceboolean

Optional, default false. Release a cluster whose checks failed. Needs a reason.

reasonstring

Optional. Why; required (not blank) with force, kept on the run.

Refusals

statuscodewhennext_action
400bad_requestThe body: an unknown field, released_through not YYYY-MM-DD or after today, or force without a reason.
403forbiddenYou lack platform:write, or you are neither an administrator of the firm nor Aleta.
404not_foundAn unknown custody or cash account, or one you do not see.
409legacy_managedYour tenant has not moved from the legacy platform: the release groups publish.
409in_useAn 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.
409nothing_stagedThe cluster has nothing staged to release.
409checks_failedA data check of the cluster failed; the detail lists them.The same POST with force and a reason.
409release_unavailableEvery check passed, but releasing is off.Ask Aleta (GET the custody or cash account).
Request
POST/v1/custody-accounts/{id}/release
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"}'

GET/v1/custody-accounts/{id}/staging-diff

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_ofdate

Only this staged day (YYYY-MM-DD); echoed as the answer's as_of.

cursor, limitstring, integer

The page: limit defaults to 100, at most 1000.

Rows

idstring

The staged holding.

as_of_datedate

The staged day.

asset_id, holding_kind, basis, currency_codestring

The holding's key.

cash_account_idstring or null

The cash account of a cash or fee row.

changestring

added (nothing published on the key), changed, or unchanged (quantity and market value as published).

staged_quantity, staged_market_value, published_quantity, published_market_valuedecimal string or null

The staged and the published values; the published ones are null for an added row.