View as Markdown

Clients

A client is the unit of onboarding on Aleta — the household, family office, or institution whose wealth the platform manages. Every other resource hangs off a client, and clients form a hierarchy: a parent client can have child clients underneath it, so a family office can split operating entities from a family trust without duplicating setup. Start here; every other v1 page assumes a client exists.

Data model

A client is a flat JSON object — no envelope, no attributes wrapper.

idstring

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

namestring

Display name shown in the product. Free-form, change-safe.

parent_client_idstring or null

Identifier of the parent client in the hierarchy. null for roots.

statusstring

active, suspended or closed. A new client is active. A closed client stays in the list.

closed_onstring or null

The first closed day (YYYY-MM-DD) of a closed client. null while the client is open.

originstring

legacy for a client mirrored from the legacy platform, v1 for one made through this API.

Reads take the structure:read scope, writes structure:write. Every write on this page takes ?dry_run=true (the answer as the write would give it, nothing kept, header Aleta-Dry-Run: true) and an Idempotency-Key header (a repeat replays the first answer; another body under the same key is 409). Each changed row is logged in the change list.


GET/v1/clients

List clients

Returns every client the authenticated credential can reach, newest last, wrapped in the standard v1 list envelope. Walk pages by passing next_cursor back as cursor until it comes back null. An unknown query parameter is a 400.

Query parameters

limitinteger

Page size. Defaults to 100, capped at 1 000. The response echoes the value actually applied.

cursorstring

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

Request
GET/v1/clients
curl -G https://api.aleta.ai/v1/clients \
  -H "Authorization: Bearer {access_token}" \
  -d limit=2
Response
{
  "data": [
    {
      "id": "019dfeb0-0511-77a7-a8be-a911ab988a0a",
      "name": "Development Client (Testing only)",
      "parent_client_id": null,
      "status": "active",
      "closed_on": null,
      "origin": "v1"
    },
    {
      "id": "019f8d08-a5fe-70d9-a454-e753f24658f4",
      "name": "E2E Child Client",
      "parent_client_id": "019dfeb0-0511-77a7-a8be-a911ab988a0a",
      "status": "active",
      "closed_on": null,
      "origin": "v1"
    }
  ],
  "next_cursor": "MjAyNi0wNy0yMyAwMzozMzowMi4wNzgzOTgrMDB8MDE5ZjhkMDgtYTVmZS03MGQ5LWE0NTQtZTc1M2YyNDY1OGY0",
  "limit": 2
}

POST/v1/clients

Create a client

Creates a new client owned by the authenticated tenant. Pass parent_client_id to nest it under an existing client, or omit it to create a root. The client gets its locked structure lens with it (see Lenses, locked), named after it and renamed with it. Returns 201 with the created resource. An unknown field is a 400.

Body parameters

namestring

Display name for the client. Required.

parent_client_idstring

Identifier of an existing client to nest under. Optional.

Request
POST/v1/clients
curl -X POST https://api.aleta.ai/v1/clients \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "E2E Child Client",
    "parent_client_id": "019dfeb0-0511-77a7-a8be-a911ab988a0a"
  }'
Response
{
  "id": "019f8d08-a5fe-70d9-a454-e753f24658f4",
  "name": "E2E Child Client",
  "parent_client_id": "019dfeb0-0511-77a7-a8be-a911ab988a0a",
  "status": "active",
  "closed_on": null,
  "origin": "v1"
}

GET/v1/clients/{id}

Retrieve a client

Fetch a single client by id. Returns the bare object — v1 single-resource responses carry no envelope. An id that doesn't exist, or that belongs to another tenant, returns the same 404 problem — the API never reveals which of the two it was.

Request
GET/v1/clients/{id}
curl https://api.aleta.ai/v1/clients/019dfeb0-0511-77a7-a8be-a911ab988a0a \
  -H "Authorization: Bearer {access_token}"
Response
{
  "id": "019dfeb0-0511-77a7-a8be-a911ab988a0a",
  "name": "Development Client (Testing only)",
  "parent_client_id": null,
  "status": "active",
  "closed_on": null,
  "origin": "v1"
}

PATCH/v1/clients/{id}

Update a client

Partially updates a client, or closes it. Only the fields present in the body are touched — omitted fields are left as they are. Returns 200 with the client. An unknown field is 400.

Body parameters

namestring

New display name.

parent_client_idstring or null

Move the client under a different parent, or pass null to detach it and make it a root. The parent cannot be the client itself or a client under it (400).

statusstring

closed, with closed_on and no other field: closes the client (offboarding). No other value is accepted.

closed_onstring

The first closed day, YYYY-MM-DD. Only with status.

Closing a client

Closing ends the client, every client under it and everything under them on closed_on, in one transaction: entities, portfolios, cash and custody accounts, the links between custody and cash accounts, ownership and tag assignments (on the entities and on the lenses) get it as the end of their period (their valid_to is the day before), and nothing is deleted. Its setup stops: limits and lenses are archived (the client's structure lens stays), webhook subscriptions are disabled and active report subscriptions paused. Access grants on the client, its sub-clients, parties, entities, accounts and lenses stop at the start of closed_on, and with them the API keys that only act through those grants; pending invites lose their grants on them (an invite left with none is cancelled). Custodian authorities on its entities go to revoke_in_progress; Aleta ends them at the bank once a person at Aleta confirms. Every changed row is logged as a change from closed_on.

Only an admin or Aleta closes a client (403 forbidden for anyone else). Nothing changes and the answer is 409 with its own code when:

  • already_closed — the client is closed already.
  • starts_after_close — a row under the client (an entity, a cash or custody account, an ownership, a tag assignment, or the link between a custody and a cash account) starts on or after closed_on; when the row has a route of its own, next_action points at deleting it (DELETE).
  • owns_outside_client — an entity under the client owns an entity outside it on closed_on; next_action points at that ownership's valid_to.
  • period_locked — a cash or custody account under the client is locked through closed_on or later.
  • legacy_managed — a row under the client comes from the legacy platform and your move to v1 is not done.
Request
PATCH/v1/clients/{id}
curl -X PATCH https://api.aleta.ai/v1/clients/019dfeb0-0511-77a7-a8be-a911ab988a0a \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Development Client (Testing only)" }'
Response
{
  "id": "019dfeb0-0511-77a7-a8be-a911ab988a0a",
  "name": "Development Client (Testing only)",
  "parent_client_id": null,
  "status": "active",
  "closed_on": null,
  "origin": "v1"
}
Close
PATCH/v1/clients/{id}
curl -X PATCH https://api.aleta.ai/v1/clients/019f8d08-a5fe-70d9-a454-e753f24658f4 \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{ "status": "closed", "closed_on": "2026-11-01" }'

DELETE/v1/clients/{id}

Delete a client

Removes an empty client. Returns 204 with an empty body on success and 404 if the id doesn't exist for this tenant — re-deleting the same id therefore returns 404, which keeps retry logic simple.

A client with sub-clients, entities, parties, lenses (also archived ones made in v1, and lenses shared with it), cash or custody accounts it manages, limits (also archived ones), benchmarks, report setups, reports, automations, custodian file deliveries, webhook subscriptions or assets it owns is not deleted: the answer is 409 in_use, the detail names what is in the way, and next_action points at closing it instead (PATCH /v1/clients/{id} with status). Archived lenses from the legacy platform, the client's structure lens and its deleted limits go with the client. The access grants on a deleted client and on its structure lens are revoked, and both are taken out of pending invites (an invite left with no grant is cancelled), each logged. A row that comes from the legacy platform answers 409 legacy_managed until your move to v1 is done.

Request
DELETE/v1/clients/{id}
curl -X DELETE https://api.aleta.ai/v1/clients/019f8d08-a5fe-70d9-a454-e753f24658f4 \
  -H "Authorization: Bearer {access_token}"

GET/v1/clients/{id}/onboarding

Retrieve the onboarding

The client's onboarding: its status (draft, in_progress, waiting or live), every step in the order a move takes them (entities, ownerships, custody_accounts, cash_accounts, custodian_authorities, transactions, tags, benchmarks, documents, access, reporting), each not_started, in_progress, waiting or done, the target and actual go-live, and how many points are open on its cash and custody accounts (open_blockers, see below). A client without onboarding reads as draft with every step not_started.

Request
GET/v1/clients/{id}/onboarding
curl https://api.aleta.ai/v1/clients/019dfeb0-0511-77a7-a8be-a911ab988a0a/onboarding \
  -H "Authorization: Bearer {access_token}"

PATCH/v1/clients/{id}/onboarding

Update the onboarding

Sets the status (draft, in_progress or waiting; live only through go-live), some steps (only the steps named change) or the go-live target (null clears it). Only an admin or Aleta: anyone else gets 403 forbidden. A refusal comes in this order: 400 for the body, 404 for a client you cannot see, then 403. Returns 200 with the onboarding. Once the client is live its status no longer changes: a status then is 409 with code already_live (steps and the target still change). An unknown field is a 400.

Request
PATCH/v1/clients/{id}/onboarding
curl -X PATCH https://api.aleta.ai/v1/clients/019dfeb0-0511-77a7-a8be-a911ab988a0a/onboarding \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{ "status": "in_progress", "steps": { "entities": "done" }, "go_live_target": "2026-12-01" }'

GET/v1/clients/{id}/onboarding/blockers

List open points

What is still open on the client's cash and custody accounts, one row per cash or custody account, to inform: go-live does not wait for it. Paged with cursor and limit like every v1 list.

  • authority_pending — a custodian authority still waiting (waiting_for_files, waiting_for_consent, awaiting_aleta or awaiting_custodian), one row per cash or custody account it covers, with authority_id and authority_status. An authority that covers no account yet is one row on its entity.
  • unknown_asset — transactions on a cash or custody account refused because the asset is unknown and still open, with transaction_count, the isins they name and since, the first.

You see the cash and custody accounts you can see.

Request
GET/v1/clients/{id}/onboarding/blockers
curl https://api.aleta.ai/v1/clients/019dfeb0-0511-77a7-a8be-a911ab988a0a/onboarding/blockers \
  -H "Authorization: Bearer {access_token}"

POST/v1/clients/{id}/go-live

Go live

Marks the client live from went_live_on (default today; not in the future). Open points do not stop it. Only an admin or Aleta: anyone else gets 403 forbidden. A refusal comes in this order: 400 for the body or the date, 404 for a client you cannot see, then 403. Returns 200 with the onboarding. A second go-live keeps the first date and logs nothing. The body is optional; an unknown field is a 400.

Request
POST/v1/clients/{id}/go-live
curl -X POST https://api.aleta.ai/v1/clients/019dfeb0-0511-77a7-a8be-a911ab988a0a/go-live \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{ "went_live_on": "2026-10-08" }'

PUT/v1/clients/{id}/period-lock

Set the period lock

Sets the same closing date on every entity of the client in one call, or opens them with an earlier date or null. The date is stored on each entity; the body, the rules and the answer are those of the entity's period lock. PUT /v1/tenants/current/period-lock does the same for every entity of the tenant. Only an admin (or Aleta, on accounts Aleta runs); 409 legacy_managed until your move to v1 is done. Takes dry_run and Idempotency-Key.

Request
PUT/v1/clients/{id}/period-lock
curl -X PUT https://api.aleta.ai/v1/clients/019dfeb0-0511-77a7-a8be-a911ab988a0a/period-lock \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{ "closed_through": null, "reason": "Correct a September dividend" }'

PUT/v1/tenants/current/period-lock

Set the tenant's period lock

Sets the same closing date on every entity of your tenant in one call, or opens them with an earlier date or null. The body, the rules and the answer are those of the entity's period lock; entities lists every entity of the tenant. A date in the future or a missing closed_through is a 400; anyone but an admin (or Aleta, on accounts Aleta runs) gets 403 forbidden; 409 legacy_managed until your move to v1 is done. Takes dry_run and Idempotency-Key.

Request
PUT/v1/tenants/current/period-lock
curl -X PUT https://api.aleta.ai/v1/tenants/current/period-lock \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{ "closed_through": "2026-09-30", "reason": "Q3 closed" }'
  • Entities — the ownership structure under a client.
  • Lenses — the consolidated views a client reports on.
  • Users — the people who can sign in to a client's workspace.