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.
idstringStable identifier for the client. A UUID — treat it as opaque.
namestringDisplay name shown in the product. Free-form, change-safe.
parent_client_idstring or nullIdentifier of the parent client in the hierarchy.
nullfor roots.statusstringactive,suspendedorclosed. A new client isactive. A closed client stays in the list.closed_onstring or nullThe first closed day (
YYYY-MM-DD) of a closed client.nullwhile the client is open.originstringlegacyfor a client mirrored from the legacy platform,v1for 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.
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
limitintegerPage size. Defaults to 100, capped at 1 000. The response echoes the value actually applied.
cursorstringOpaque cursor from the previous page's
next_cursor. Omit for the first page.
curl -G https://api.aleta.ai/v1/clients \
-H "Authorization: Bearer {access_token}" \
-d limit=2
{
"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
}
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
namestringDisplay name for the client. Required.
parent_client_idstringIdentifier of an existing client to nest under. Optional.
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"
}'
{
"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"
}
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.
curl https://api.aleta.ai/v1/clients/019dfeb0-0511-77a7-a8be-a911ab988a0a \
-H "Authorization: Bearer {access_token}"
{
"id": "019dfeb0-0511-77a7-a8be-a911ab988a0a",
"name": "Development Client (Testing only)",
"parent_client_id": null,
"status": "active",
"closed_on": null,
"origin": "v1"
}
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
namestringNew display name.
parent_client_idstring or nullMove the client under a different parent, or pass
nullto detach it and make it a root. The parent cannot be the client itself or a client under it (400).statusstringclosed, withclosed_onand no other field: closes the client (offboarding). No other value is accepted.closed_onstringThe first closed day,
YYYY-MM-DD. Only withstatus.
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 afterclosed_on; when the row has a route of its own,next_actionpoints at deleting it (DELETE).owns_outside_client— an entity under the client owns an entity outside it onclosed_on;next_actionpoints at that ownership'svalid_to.period_locked— a cash or custody account under the client is locked throughclosed_onor later.legacy_managed— a row under the client comes from the legacy platform and your move to v1 is not done.
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)" }'
{
"id": "019dfeb0-0511-77a7-a8be-a911ab988a0a",
"name": "Development Client (Testing only)",
"parent_client_id": null,
"status": "active",
"closed_on": null,
"origin": "v1"
}
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 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.
curl -X DELETE https://api.aleta.ai/v1/clients/019f8d08-a5fe-70d9-a454-e753f24658f4 \
-H "Authorization: Bearer {access_token}"
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.
curl https://api.aleta.ai/v1/clients/019dfeb0-0511-77a7-a8be-a911ab988a0a/onboarding \
-H "Authorization: Bearer {access_token}"
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.
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" }'
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_aletaorawaiting_custodian), one row per cash or custody account it covers, withauthority_idandauthority_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, withtransaction_count, theisinsthey name andsince, the first.
You see the cash and custody accounts you can see.
curl https://api.aleta.ai/v1/clients/019dfeb0-0511-77a7-a8be-a911ab988a0a/onboarding/blockers \
-H "Authorization: Bearer {access_token}"
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.
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" }'
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.
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" }'
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.
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" }'