Skip to main content

Workspace members

A workspace is shared with other admins through membership rows. Each member holds exactly one role, and that role decides what every workspace route will let them do. These routes read the roster and change it.

All routes require the configured API-key header and trusted principal headers (X-User-Subject, plus the optional role/unit/level claims) exactly as the other principal-bearing routes do; see Conversations and memory. A caller who is not a member of the workspace gets 404 on every route below, the same answer an unknown workspace id gets.

Workspace roles​

owner, partner, and viewer are workspace roles — a person's standing inside one workspace. They are not the same thing as a partner, the integrating organisation that uploads documents through Kafka. The same admin can be an owner of one workspace and a viewer of another, and belonging to the integrating organisation does not itself grant a workspace role.

The routes enforce roles with one shared check, WorkspaceAccess.require(...) in src/aceh_rag/api/workspaces.py:327, which answers 403 with {"detail": "This action requires one of: <roles>"}. The permissions below are read from those calls, not from product intent.

CapabilityownerpartnerviewerCode check
Read the workspace, its sources, documents, artifacts, and rosterYesYesYes_access() only, no require: GET sources workspaces.py:584, documents :922, artifacts :977, artifact content :1004, members :1094
Upload documents, replace a documentYesYesNoaccess.require(OWNER, PARTNER): upload :850, replace :884
Save the source set, select or unselect one documentYesYesNoaccess.require(OWNER, PARTNER): save :625, select :683, unselect :717
Delete a workspace uploadYesYesNoaccess.require(OWNER, PARTNER): :756
Delete an artifactAnyOnly one they createdNo:1063, plus the partner's own-artifact check at :1064
Rename or delete the workspaceYesNoNoaccess.require(OWNER): :501, :528
Invite a member, change a member's roleYesNoNoaccess.require(OWNER): :1122
Remove another memberYesNoNoaccess.require(OWNER): :1169
Leave the workspace (remove yourself)Transfer ownership firstYesYes:1168 — self-removal skips the owner check; the last-owner guard at :1174 returns 422
Transfer ownershipYesNoNoaccess.require(OWNER): :1208

A viewer may read and remove themselves. Other writes require owner or partner and answer 403 with This action requires one of: owner, partner when refused; administrative writes require owner and answer 403 with This action requires one of: owner. A partner may delete only artifacts they created. An owner must transfer ownership before leaving.

A workspace always has exactly one owner. The repository refuses a second owner (WorkspaceOwnerExistsError) and refuses to leave a workspace ownerless (LastWorkspaceOwnerError); both surface as 422 below.

Member object​

GET /workspaces/{workspace_id}/members and the two routes that return a member share one shape:

FieldTypeMeaning
admin_idstring (UUID)The member's admin subject, as supplied in X-User-Subject.
rolestringowner, partner, or viewer.
invited_bystring or nullSubject of the admin who added them; null when the row carries no recorded inviter.

GET /workspaces/{workspace_id}/members​

Returns every member, owner first, then by invitation order. Any member, including a viewer, may call it: a viewer can already see every answer and document in the workspace, so hiding the roster protects nothing.

{
"members": [
{"admin_id": "0f2b0000-0000-4000-8000-000000000001", "role": "owner", "invited_by": null},
{"admin_id": "0f2b0000-0000-4000-8000-000000000002", "role": "partner", "invited_by": "0f2b0000-0000-4000-8000-000000000001"}
]
}
StatusDetailWhen
200RosterThe caller is a member.
401{"detail": "Invalid or missing API key"}The API-key header is absent or wrong.
401{"detail": "Authenticated principal required"}Principal enforcement is on and identity is absent.
401{"detail": "Invalid authenticated principal"}Principal enforcement is on and identity resolution fails, or the resolved principal subject is not a UUID, regardless of principal enforcement.
404{"detail": "Workspace not found"}Unknown workspace id, or the caller is not a member. The two are deliberately indistinguishable.
422Validation array (detail) with msg: "Value error, workspace_id must be a valid UUID"workspace_id is not a UUID.

POST /workspaces/{workspace_id}/members​

Invites an admin, or changes an existing member's role — the same call. Inviting somebody already present is a role change rather than an error, so a client never has to know which verb applies before the user acts. owner only.

{"admin_id": "0f2b0000-0000-4000-8000-000000000002", "role": "partner"}

extra fields are refused with 422. admin_id must be a UUID and role must be one of the three enum values.

StatusDetailWhen
200Member objectThe invite or role change was applied.
401{"detail": "Invalid or missing API key"}The API-key header is absent or wrong.
401{"detail": "Authenticated principal required"}Principal enforcement is on and identity is absent.
401{"detail": "Invalid authenticated principal"}Principal enforcement is on and identity resolution fails, or the resolved principal subject is not a UUID, regardless of principal enforcement.
403{"detail": "This action requires one of: owner"}The caller is a partner or viewer. Nothing changes.
404{"detail": "Workspace not found"}Unknown workspace id, or the caller is not a member.
422Validation arrayNon-UUID workspace_id or admin_id, missing admin_id or role, unknown role, or a forbidden extra field.
422{"detail": "This workspace already has an owner. Transfer ownership instead of inviting a second one."}The request tries to make somebody else an owner. Use PUT …/owner.
422{"detail": "A workspace must always have an owner."}The request demotes the only owner.

Changing a role is not a way to hand over the workspace: the owner slot moves only through the transfer route.

DELETE /workspaces/{workspace_id}/members/{admin_id}​

Removes a member. An owner removes anyone; any member may remove themselves, so a viewer who no longer wants the workspace in their list does not need the owner. Success returns 204 with no body.

StatusDetailWhen
204Empty bodyThe member row was deleted.
401{"detail": "Invalid or missing API key"}The API-key header is absent or wrong.
401{"detail": "Authenticated principal required"}Principal enforcement is on and identity is absent.
401{"detail": "Invalid authenticated principal"}Principal enforcement is on and identity resolution fails, or the resolved principal subject is not a UUID, regardless of principal enforcement.
403{"detail": "This action requires one of: owner"}The caller is not the owner and admin_id is not the caller's own subject. Nothing changes.
404{"detail": "Workspace not found"}Unknown workspace id, or the caller is not a member.
404{"detail": "Member not found"}The caller is allowed to remove this id, but no such member exists here. Nothing changes.
422Validation arrayworkspace_id or admin_id is not a UUID.
422{"detail": "A workspace must always have an owner. Transfer ownership before leaving."}The owner tries to remove themselves while they are the only owner.

PUT /workspaces/{workspace_id}/owner​

Hands the workspace to another admin, atomically. PUT on a singular sub-resource, because a workspace has exactly one owner and this call names who it is. owner only.

{"admin_id": "0f2b0000-0000-4000-8000-000000000002"}

The outgoing owner becomes a partner: they keep working in the workspace they were running and lose only administration. The target becomes owner; if the target was not a member, the row is created, so a transfer can also be the first invite. The caller keeps access as a partner. extra fields are refused with 422, and admin_id must be a UUID.

StatusDetailWhen
200Member objectThe new owner, with role: "owner".
401{"detail": "Invalid or missing API key"}The API-key header is absent or wrong.
401{"detail": "Authenticated principal required"}Principal enforcement is on and identity is absent.
401{"detail": "Invalid authenticated principal"}Principal enforcement is on and identity resolution fails, or the resolved principal subject is not a UUID, regardless of principal enforcement.
403{"detail": "This action requires one of: owner"}The caller is a partner or viewer. Nothing changes.
404{"detail": "Workspace not found"}Unknown workspace id, or the caller is not a member.
422Validation arrayNon-UUID workspace_id or admin_id, missing admin_id, or a forbidden extra field.

Transferring to the current owner is a no-op that returns their row. Ownership is never cleared: transferring demotes the outgoing owner rather than leaving the workspace ownerless.