Skip to main content

Workspace members API

Workspace membership (add/remove principals).

This page lists 9 endpoints (OpenAPI tag workspace-members). It is generated from openapi.json; see the REST API overview for authentication, CSRF and error conventions.

MethodPathOperation idNotes
GET/api/ws/{workspace}/access-requestslist_workspace_access_requestsGET /ws/\{workspace\}/access-requests?status= — the workspace's join requests filtered by status (default pending).
POST/api/ws/{workspace}/access-requests/{id}/approveapprove_access_requestPOST /ws/\{workspace\}/access-requests/\{id\}/approve — approve a pending request: add the requester as a member with the chosen role + optional expiry via the...
POST/api/ws/{workspace}/access-requests/{id}/denydeny_access_requestPOST /ws/\{workspace\}/access-requests/\{id\}/deny — deny a pending request with an optional note.
POST/api/ws/{workspace}/impersonatews_impersonate_startPOST /api/ws/\{workspace\}/impersonate — a ws_admin of \{workspace\} (or a platform_admin) starts impersonating a MEMBER of that workspace.
GET/api/ws/{workspace}/memberslist_members
POST/api/ws/{workspace}/membersadd_member
DELETE/api/ws/{workspace}/members/{ptype}/{pid}remove_member
GET/api/ws/{workspace}/roleslist_workspace_rolesGET /ws/\{workspace\}/roles — the workspace-scope role catalog with each role's direct permissions.
GET/api/ws/{workspace}/shareslist_workspace_sharesGET /ws/\{workspace\}/shares — every OBJECT grant whose object lives in THIS workspace (charts, dashboards, datasets, folders, saved queries, apps), enriched...

GET /api/ws/{workspace}/access-requests​

Operation id: list_workspace_access_requests. Auth: Session cookie. Forbidden (403): Missing workspace permission.

GET /ws/\{workspace\}/access-requests?status= — the workspace's join requests filtered by status (default pending). Gated on workspace:manage_members.

ParameterInTypeRequiredDescription
workspacepathstringyesWorkspace slug
statusquerystringnoFilter by status (default: pending)

Response 200 (application/json): array of AccessRequestDto

FieldTypeRequiredDescription
idstringyes
workspace_idstringyes
workspace_namestring, nullableno
requester_idstringyes
requester_namestring, nullableno
requested_rolestringyes
reasonstringyes
statusstringyes
decided_bystring, nullableno
decided_atstring, nullableno
decision_notestring, nullableno
expires_atstring, nullableno
created_atstringyes
StatusMeaning
200The workspace's join requests, newest first
401Not authenticated
403Missing workspace permission
500Internal error

POST /api/ws/{workspace}/access-requests/{id}/approve​

Operation id: approve_access_request. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing workspace permission.

POST /ws/\{workspace\}/access-requests/\{id\}/approve — approve a pending request: add the requester as a member with the chosen role + optional expiry via the repo's single grant path, then mark it approved. The repo enforces the cross-workspace guard (NotFound → 404) and pending guard (Conflict → 409).

ParameterInTypeRequiredDescription
workspacepathstringyesWorkspace slug
idpathstring (uuid)yesAccess-request id
workspacepathstringyes

Request body (application/json): ApproveAccessRequest

FieldTypeRequiredDescription
rolestring, nullablenoRole to grant the requester; defaults to the originally requested role.
expires_atstring (date-time), nullablenoOptional time-boxed expiry for the membership grant.

Response 200 (application/json): Ack

FieldTypeRequiredDescription
okbooleanyes
StatusMeaning
200Request approved and member added
401Not authenticated
403Missing workspace permission
404No such request in this workspace
409Request already decided
500Internal error

POST /api/ws/{workspace}/access-requests/{id}/deny​

Operation id: deny_access_request. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing workspace permission.

POST /ws/\{workspace\}/access-requests/\{id\}/deny — deny a pending request with an optional note. The repo enforces the cross-workspace + pending guards.

ParameterInTypeRequiredDescription
workspacepathstringyesWorkspace slug
idpathstring (uuid)yesAccess-request id
workspacepathstringyes

Request body (application/json): DenyAccessRequest

FieldTypeRequiredDescription
notestring, nullablenoOptional note recorded on the denied request.

Response 200 (application/json): Ack

FieldTypeRequiredDescription
okbooleanyes
StatusMeaning
200Request denied
401Not authenticated
403Missing workspace permission
409Request not pending or not in this workspace
500Internal error

POST /api/ws/{workspace}/impersonate​

Operation id: ws_impersonate_start. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Not a ws_admin, already impersonating, target not a member, target is a ws_admin here, or the target holds ANY platform-scoped role grant — wider than the platform endpoint's rule, because the overlay this one writes is workspace-scoped and no /api/control-plane route confines such an overlay.

POST /api/ws/\{workspace\}/impersonate — a ws_admin of \{workspace\} (or a platform_admin) starts impersonating a MEMBER of that workspace. The resulting overlay is workspace-scoped: WsContext confines it to \{workspace\} even though the target may belong to others (see the containment guard there).

Guards run in a deliberate order so a caller learns nothing before its own authority is proven: (1) not already impersonating; (2) caller is ws_admin here or a platform_admin; (3) the shared target guards (not-self, reason non-empty, target exists, and — because the overlay THIS endpoint writes is workspace-scoped — the target holds NO platform-scoped role grant at all; validate_impersonation_target, and read its ⚠ paragraph: that third guard is what stops this endpoint minting a scoped overlay with platform authority, which no /api/control-plane route confines, and it is deliberately WIDER here than on the platform endpoint); (4) the target is a MEMBER of this workspace and is NOT...

ParameterInTypeRequiredDescription
workspacepathstringyesWorkspace slug

Request body (application/json): StartWsImpersonation

FieldTypeRequiredDescription
target_user_idstringyes
reasonstringyes
passwordstring, nullablenoRequired for a local-auth caller (step-up). OIDC callers re-auth via OIDC.
StatusMeaning
200Impersonation started; the session now resolves to the target, scoped to this workspace
400Bad target / empty reason
401Not authenticated, or wrong step-up password
403Not a ws_admin, already impersonating, target not a member, target is a ws_admin here, or the target holds ANY platform-scoped role grant — wider than the platform endpoint's rule, because the overlay this one writes is workspace-scoped and...
409OIDC caller: re-authenticate at authorize_url, then the callback creates the overlay
500Internal error

GET /api/ws/{workspace}/members​

Operation id: list_members. Auth: Session cookie. Forbidden (403): Missing workspace permission.

ParameterInTypeRequiredDescription
workspacepathstringyesWorkspace slug

Response 200 (application/json): array of MemberDto

FieldTypeRequiredDescription
principal_typestringyes
principal_idstringyes
rolestringyes
principal_namestring, nullableno
granted_by_namestring, nullableno
added_atstring (date-time)yes
is_platform_adminbooleanyes
StatusMeaning
200Members of the workspace
401Not authenticated
403Missing workspace permission
500Internal error

POST /api/ws/{workspace}/members​

Operation id: add_member. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing workspace permission.

ParameterInTypeRequiredDescription
workspacepathstringyesWorkspace slug

Request body (application/json): AddMember

FieldTypeRequiredDescription
principal_typestringyes
principal_idstringyes
rolestringyes

Response 200 (application/json): Ack

FieldTypeRequiredDescription
okbooleanyes
StatusMeaning
200Member added
401Not authenticated
403Missing workspace permission
500Internal error

DELETE /api/ws/{workspace}/members/{ptype}/{pid}​

Operation id: remove_member. Auth: Session cookie and X-Egret-CSRF header. Forbidden (403): Missing workspace permission.

ParameterInTypeRequiredDescription
workspacepathstringyesWorkspace slug
ptypepathstringyesPrincipal type (user | group)
pidpathstring (uuid)yesPrincipal id
workspacepathstringyes

Response 200 (application/json): Ack

FieldTypeRequiredDescription
okbooleanyes
StatusMeaning
200Member removed
401Not authenticated
403Missing workspace permission
500Internal error

GET /api/ws/{workspace}/roles​

Operation id: list_workspace_roles. Auth: Session cookie. Forbidden (403): Missing workspace permission.

GET /ws/\{workspace\}/roles — the workspace-scope role catalog with each role's direct permissions. Read-only, so a workspace admin can SEE what each role grants; gated on workspace:manage_members (the same gate as the member routes) rather than the platform-level role:read.

ParameterInTypeRequiredDescription
workspacepathstringyesWorkspace slug

Response 200 (application/json): array of WsRoleDto

FieldTypeRequiredDescription
idstringyes
namestringyes
descriptionstringyes
parent_role_idstring, nullableno
permissionsWsPermDto[]yes
StatusMeaning
200Workspace-scope roles with their permissions
401Not authenticated
403Missing workspace permission
500Internal error

GET /api/ws/{workspace}/shares​

Operation id: list_workspace_shares. Auth: Session cookie. Forbidden (403): Missing workspace permission.

GET /ws/\{workspace\}/shares — every OBJECT grant whose object lives in THIS workspace (charts, dashboards, datasets, folders, saved queries, apps), enriched with object name, role name, principal name and effect. Read-only, so a workspace admin can SEE what has been shared; gated on workspace:manage_members (the same gate as the member/role routes). The object set is restricted to this workspace in SQL — a grant on an object in another workspace is never returned (cross-workspace IDOR guard).

ParameterInTypeRequiredDescription
workspacepathstringyesWorkspace slug

Response 200 (application/json): array of ObjectGrantRollupDto

FieldTypeRequiredDescription
kindstringyes
object_idstringyes
object_namestring, nullableno
principal_typestringyes
principal_idstringyes
principal_namestring, nullableno
rolestringyes
effectstringyes
expires_atstring, nullablenoOptional expiry (RFC3339). None = never expires. This is the management roll-up, so an expired grant is still returned (not filtered).
StatusMeaning
200Object grants whose object lives in this workspace
401Not authenticated
403Missing workspace permission
500Internal error