Groups and modules
v1 has two roles, admin and user. An admin sees everything in the tenant and is in no group. What a user can see and do comes from the user's groups: a group is a set of the tenant's modules, each at the level read or read_write. A user can be in several groups, and the rights add up; the higher level wins. Aleta ships two standard groups, Viewer (every module at read) and Editor (every module at read_write), both without the audit module.
Every v1 route belongs to a module. A read needs the module at read, a write at read_write (a tag preview writes nothing and needs read); a few routes (most platform writes, webhook subscriptions) are for admins only, and a user gets 403 forbidden there. The scopes of your token or API key stay the ceiling: a module never opens a route your scopes do not. Access administration (users, invites, API keys, access grants, groups) is no module: only an admin changes it.
Modules
The module catalog: what a group can hold. Every caller with access to the tenant can read it, without any scope. A module that can only be read lists only read in levels.
keystringcore(clients, entities, cash and custody accounts, holdings, performance, lenses, tags),market_data,transactions,registrations,limits,benchmarks,risk,private_markets,documents,reports,operations(reconciliation, imports, jobs, events, data status),audit(read only),esg,wealth_maporallocation.descriptionstringWhat the module covers.
levelsstring[]["read", "read_write"], or["read"].
{
"data": [
{
"key": "core",
"description": "Clients, entities, cash and custody accounts, holdings, performance, lenses and tags",
"levels": ["read", "read_write"]
},
{
"key": "audit",
"description": "Audit events: who did what, and when",
"levels": ["read"]
}
],
"next_cursor": null,
"limit": 15
}
Your tenant
The tenant you call, one object. Needs platform:read.
id, name, region, status, plan, created_atstringThe tenant.
regionis the region its data lives in, asregion_idonGET /v1/residency(for exampleaws-eu-central-1).status,planandcreated_atcan benull.modulesstring[]The modules the tenant has; a group can only hold these.
flagsobjectWhat Aleta has sold the tenant:
ai_enabled,cube_enabled,api_enabled,mcp_enabled.api_enabledandmcp_enabledsay sold, not whether a call works.ai_enabledistrueunless AI is switched off for the tenant.white_label_levelstringsimple,standardorextended.
{
"id": "3f600564-957b-4aab-96ed-fd0118cfd805",
"name": "development-client-testing-only",
"region": "aws-eu-central-1",
"status": "active",
"plan": null,
"created_at": "2026-07-01T09:00:00Z",
"modules": ["core", "market_data", "transactions", "registrations", "limits", "benchmarks", "risk", "private_markets", "documents", "reports", "operations", "audit", "esg", "wealth_map", "allocation"],
"flags": { "ai_enabled": true, "cube_enabled": false, "api_enabled": true, "mcp_enabled": false },
"white_label_level": "simple"
}
The group
idstringStable identifier. A UUID.
namestringUnique among the groups in use, in any case.
kindstringstandard(Viewer and Editor: the name stays and they are never archived) orcustom.modulesobject[]{module, level}per module, in catalog order.member_countintegerThe users in the group now.
membersobject[]{user_id, added_at, origin}per user, on a single group and on a write's answer. Not in a list.originstringlegacyorv1. Alegacygroup is read-only until the tenant has moved (409 legacy_managed).created_by_user_id, created_at, updated_atstringWho made the group, and when.
created_by_user_idisnullon the standard groups and onlegacygroups.archived_at, archived_by, archive_reasonstring or nullSet while the group is archived. An archived group gives no rights; its members stay in it.
List groups
An admin with access:read gets every group; anyone else gets the groups they are in. Pages in id order. An unknown query parameter is a 400.
include_archivedbooleanArchived groups are left out unless
true.limit, cursorinteger, stringPage size (default 100, at most 1 000) and the previous page's
next_cursor.
{
"data": [
{
"id": "019e9a11-2b6c-7d3e-8f10-6a2b3c4d5e6f",
"name": "Viewer",
"kind": "standard",
"modules": [{ "module": "core", "level": "read" }],
"member_count": 4,
"origin": "v1",
"created_by_user_id": null,
"created_at": "2026-10-16T20:00:00.000000Z",
"updated_at": "2026-10-16T20:00:00.000000Z",
"archived_at": null,
"archived_by": null,
"archive_reason": null
}
],
"next_cursor": null,
"limit": 100
}
Get a group
The group with its members. 404 for a group you are not in, unless you are an admin with access:read.
Change groups
Only an admin changes groups: a person, or an API key an admin owns (Aleta's fleet key too); the token needs access:write, and anyone else gets 403 forbidden. Every change takes ?dry_run=true and Idempotency-Key (see Writes) and is logged once. Times are RFC 3339 UTC with microseconds. An unknown body field is a 400; an {id} that is not a UUID, or a group that does not exist, is a 404. A legacy group answers 409 legacy_managed until the tenant has moved. Every 409 names its rule in code; the group rules below also carry next_action (null when there is no step to take; for an archived group the restore call, PATCH /v1/groups/{id} with status).
Create a group
201 with the group, its members included (an empty list).
namestringTrimmed; must not be empty and holds at most 100 characters (
400).409 name_takenwhen a group in use has the name, in any case.modulesobject[]Optional:
[{module, level}],levelone ofreadorread_write.400for an unknown module,access, a level the module does not allow (auditis onlyread) or a module named twice. Every tenant has every module.
curl -X POST https://api.aleta.ai/v1/groups -H "Authorization: Bearer {access_token}" -H "Idempotency-Key: {uuid}" -H "Content-Type: application/json" -d '{"name": "Analysts", "modules": [{"module": "core", "level": "read"}, {"module": "reports", "level": "read_write"}]}'
Update a group
Renames the group or brings an archived group back. 200 with the group and its members.
namestringThe new name, by the same rules as on create.
409 standard_groupfor a new name on a standard group.statusstringactivebrings back an archived group.archivedis a400: archive withDELETE.
At least one of the two (400 otherwise). 409 name_taken when the new name, or the name of a group brought back, is in use.
curl -X PATCH https://api.aleta.ai/v1/groups/{id} -H "Authorization: Bearer {access_token}" -H "Idempotency-Key: {uuid}" -H "Content-Type: application/json" -d '{"status": "active"}'
Archive a group
Archives the group: 204, also when it is archived already. Its rights end within 5 seconds; its members stay. ?reason= is kept as archive_reason. Any query parameter other than reason and dry_run is a 400. 409 standard_group for a standard group.
curl -X DELETE "https://api.aleta.ai/v1/groups/{id}?reason=team%20disbanded" -H "Authorization: Bearer {access_token}" -H "Idempotency-Key: {uuid}"
Set the modules
Body {modules}: the whole list as [{module, level}], replacing the stored one; modules is required. 200 with the group. 400 for an unknown module, access, a level the module does not allow (audit is only read) or a module named twice; 409 in_use for an archived group (bring it back with PATCH status: active first).
curl -X PUT https://api.aleta.ai/v1/groups/{id}/modules -H "Authorization: Bearer {access_token}" -H "Idempotency-Key: {uuid}" -H "Content-Type: application/json" -d '{"modules": [{"module": "core", "level": "read_write"}, {"module": "audit", "level": "read"}]}'
Add a member
Body {user_id}, a UUID (400 otherwise). 201 with the group and its members. The group is checked before the person: 404, 409 legacy_managed, 409 in_use for an archived group. Then the person: 404 for anyone GET /v1/users/{id} does not find (Aleta staff, API keys, users without access to the tenant) and 409 admin_in_no_group for an admin (admins are in no group); 409 already_member for a user in the group already.
curl -X POST https://api.aleta.ai/v1/groups/{id}/members -H "Authorization: Bearer {access_token}" -H "Idempotency-Key: {uuid}" -H "Content-Type: application/json" -d '{"user_id": "019e9a40-1a2b-7c3d-8e4f-5a6b7c8d9e0f"}'
Remove a member
Takes the user out: 204, also on an archived group. 404 when the user is not in the group or either id is not a UUID. A membership from the legacy platform answers 409 legacy_managed until the tenant has moved. A replay under the same Idempotency-Key after the user left answers the first 204.
curl -X DELETE https://api.aleta.ai/v1/groups/{id}/members/{user_id} -H "Authorization: Bearer {access_token}" -H "Idempotency-Key: {uuid}"