Skip to content

Groups API

The groups API manages user groups and group-based access grants on knowledge spaces (projects). Assigning access at the group level (rather than per user) is the foundation for directory-driven provisioning and role mapping.

All endpoints require an authenticated admin session (aig_admin cookie). The group endpoints additionally require the ki_manager, tenant_admin or admin role. The two org-share endpoints at the bottom of this page are the exception: any project owner may use them, including a plain member. The base URL is https://ai-api-admin.myra.eu/admin/v1.

A ki_manager (the three-tier "Fachbereich" role between member and tenant_admin) may create groups and read all groups in its tenant, but may mutate (rename, delete, change membership, grant/revoke) only the groups it created (created_by). tenant_admin and admin manage all groups in scope. This keeps a ki_manager from churning the membership of a group it does not own — which would confer/revoke access on resources granted to that group.

Every group, membership, and grant is tenant-scoped: a tenant_admin only ever sees and mutates groups in their own tenant. A platform admin may operate across tenants by passing an explicit tenant_id on create/list. A reference to a resource (group id, user, project) in another tenant is rejected with 404 — the API does not confirm the existence of out-of-tenant resources.

Group grants resolve through membership: a project's effective role for a user is MAX(direct member role, best group-grant role) (owner > editor > viewer). Removing a user from a group immediately revokes the group-derived access; it never downgrades a higher direct role.

Every create / update / delete / grant / revoke is written to the audit log.


Groups

List groups

GET /admin/v1/groups

Returns the groups in the caller's tenant. Each row carries id, tenant_id, name, description, created_by, created_at and updated_at (Unix seconds), plus a member_count. A platform admin must pass ?tenant_id=<ID>; the request returns 400 if it is omitted. GET /admin/v1/groups/{id} returns the same fields and additionally attaches the group's members and grants arrays.

Creating a group

POST /admin/v1/groups

Field Type Required
name string yes — non-empty; unique per tenant. A clash returns 409.
description string no
tenant_id string only for a platform admin caller (400 if omitted by an admin).

Returns 201 with { "id": "<group id>" }.

Get a group

GET /admin/v1/groups/{id}

Returns the group plus its members and grants arrays. A group in another tenant returns 404.

Updating a group

PATCH /admin/v1/groups/{id}

Body { "name"?, "description"? }. A name clash returns 409; an empty name returns 400.

Deleting a group

DELETE /admin/v1/groups/{id}

Hard-deletes the group; its memberships and grants are removed with it. Returns 200.


Members

List members

GET /admin/v1/groups/{id}/members

List add-member candidates

GET /admin/v1/groups/{id}/members/candidates

Returns the users of the group's tenant who are not already members — the data source for the add-member picker's browsable list. Each entry is { id, email, name } (no role/login/status metadata). The server excludes, and the client cannot broaden: soft-deleted users, platform admin users (a non-admin caller cannot add an admin target, so they are never offered), and existing members. The result is capped at 1000 rows (ordered by email); anyone beyond the cap is still addable through the add-by-email path below.

Authorized like the add-member mutation, not the members-list read: require_ki_manager and can_manage_group (admin ∨ tenant_admin ∨ the group's creator). A ki_manager who does not manage the group → 403; a caller in another tenant → 404 (no existence oracle). Because a group-managing ki_manager can read this, the endpoint effectively exposes the tenant's non-admin user roster (email + name) to that trusted intra-tenant tier — this is the intended, minimal consequence of an in-tenant "browse existing members" picker.

Adding a member

POST /admin/v1/groups/{id}/members

Field Type Required
user_id string yes — must belong to the same tenant as the group (cross-tenant or unknown user → 404).

Returns 201. Re-adding an existing member is idempotent.

Removing a member

DELETE /admin/v1/groups/{id}/members/{user_id}

Idempotent — removing a non-member still returns 200.


Grants (group → resource)

A grant gives every member of the group a role on a resource. Creating a grant additionally requires can_share: the caller must be the owner of the resource, a tenant_admin, or a platform admin. A non-owner attempting to grant a resource they do not own returns 403.

List a group's grants

GET /admin/v1/groups/{id}/grants

Creating a grant

POST /admin/v1/groups/{id}/grants

Field Type Required
resource_type project yes — any other value returns 400. (Agent grants are a planned addition.)
resource_id string yes — must be a resource in the same tenant as the group (cross-tenant or unknown → 404).
role owner | editor | viewer yes — any other value returns 400.

Returns 201. Re-granting the same group on the same resource updates the role (idempotent). A group granted owner confers the resource's owner powers (including deletion) on every member; group-conferred owners count toward the resource's last-owner protection.

Revoke a grant

DELETE /admin/v1/groups/{id}/grants/{resource_type}/{resource_id}

Returns 200 (idempotent).


Rejected input summary

Input Rejected when Status
any group route caller is not ki_manager/tenant_admin/admin 403
org-share routes caller is viewer/demouser, or has no/unknown role 403
org-share routes caller is not the project owner (nor tenant_admin/admin) 403 (same tenant) / 404 (cross-tenant or unknown)
{id} group is in another tenant / unknown 404
name empty, or duplicate within the tenant 400 / 409
tenant_id omitted by a platform admin on create/list 400
members.user_id unknown, or in a different tenant than the group 404
grants.resource_type not project 400
grants.resource_id unknown, or in a different tenant than the group 404
grants.role not owner/editor/viewer 400
grant create/revoke caller cannot can_share the resource (not owner, nor tenant_admin/admin) 403/404
any mutation a ki_manager mutating a group it did not create 403

Org-share

Org-share makes a project accessible to the whole tenant. It is a tenant capability that an admin/tenant_admin can deactivate: PATCH /admin/v1/tenants/{id} with { "org_share_enabled": 0 | 1 } (audited). When the capability is 0, no new org-share can be created and every existing org-share stops granting access at resolve time — retroactive, with no deny rows. The toggle and a project's org_grant surface via GET /admin/auth/me (tenant_org_share_enabled) and GET /admin/v1/projects/{id} (org_grant).

GET /admin/v1/projects/{id} additionally returns org_access — a server-derived VIEW of whether the org grant actually resolves to access, for the Members surface. It is { "role": "editor" | "viewer" } only when a grant row exists and the tenant capability is on; it is absent otherwise. This is distinct from org_grant (the raw grant row that drives the share/unshare toggle): when the capability is switched off, the grant row — and thus org_grant — is unchanged, but org_access disappears, because no tenant user has org access at resolve time. The role in org_access is read from the same facts the access resolver reads, so it can never claim access the resolver would not grant; a grant that fails to resolve to a recognised role yields no org_access (fail closed).

Share a project with the organization

POST /admin/v1/projects/{id}/org-share — body { "role": "editor" | "viewer" }.

The admin UI sends viewer. Its control is a single toggle with no role picker, so whatever it sends is what every project owner grants without being asked — and editor is not a read role: it is the level that gates creating, uploading, moving and deleting a project's knowledge files. A share meant to let colleagues see a project must not hand the whole tenant delete rights on its documents. editor remains accepted for a deliberate, API-driven share.

Gated by require_author AND can_share (owner ∨ tenant_admin ∨ admin) AND the project's tenant org_share_enabled = 1.

This is available to any project owner, including a plain member — it is no longer restricted to ki_manager/tenant_admin/admin. The reason is consistency with the per-user path: POST /admin/v1/projects/{id}/members has always been owner-gated with no manager-role requirement and accepts role: "owner", so an owner could already grant more, one colleague at a time. require_author still refuses the read-only roles (viewer, demouser) and fails closed on an absent or unrecognised role. Ownership may be direct or inherited from a group that was granted the owner role on the project.

Role is capped at editor/viewer — never owner (an org-owner would make every tenant user a project owner and could be laundered into a non-revocable group grant). Returns 403 org_share_disabled when the capability is off, 503 if the capability could not be read because of a transient fault (safe to retry), 404 if the project's tenant no longer exists, and 400 for role: "owner" or any non-editor/viewer value. A transient failure to read the project itself is likewise a 503, never a misleading permanent 404. Idempotent (re-share updates the role — including upgrading an existing viewer org grant to editor).

Stop sharing

DELETE /admin/v1/projects/{id}/org-share — gated by require_author AND can_share (but not the capability flag, so cleanup works when it is off). Available to any project owner, like sharing. Returns 200 (idempotent).


Role → capability matrix

ki_manager is the three-tier role between member and tenant_admin ("Fachbereich" manager). Least-privilege: it adds group management to member-level capabilities, nothing more. (Org-share used to be part of that step up; it was moved to project ownership, so it is no longer what distinguishes a ki_manager.)

Capability admin tenant_admin ki_manager member finance viewer demouser
Manage groups (own / created) all all own only – – – –
Org-share a project (owned) yes yes yes yes – – –
Gateways / Users / Tenants / Providers / MCP admin yes yes (scoped) – – – – –
Analytics (/stats) scope tenant-wide tenant-wide own data own data own data own data own data
Create projects / agents, run inference yes yes yes yes inference only – –
Assign roles all up to ki_manager – – – – –

finance is a cost-console role: it can run inference but cannot author (create projects, agents, or workflows) or administer anything. Its distinct surface is Settings → Costs → User Costs (/finance) and the tenant billing/cost console.

Scope: "Anwender" = the tenant / operating organization; org-share deactivation is tenant-scoped, enforced at grant-create AND at the resolver. The additive model has no per-individual opt-out. "Erweiterte Agentenkonfig" is delivered via agent sharing.

Superseded: an earlier reading additionally treated org-sharing as making "mit Org teilen" a KI-Manager Fachbereich capability, and gated the endpoints accordingly so that a plain member could not org-broadcast even a project they owned. Real user demand contradicted that reading — a project owner with no manager role had no way to share their project and was adding members one by one — so the gate is now project ownership plus the tenant capability flag. The tenant-scoped deactivation requirement is unchanged and remains the admin control. Note that agent org-share (api-reference/agents.md) is still ki_manager-gated, so the two resources currently differ.