Members
Who's in a workspace, and what they can do.
A member is a Supabase user with a row in workspace_members joining them to a workspace with a role. One user can be a member of many workspaces; each membership has its own role.
Roles
There are four roles, ranked low → high. Each row grants the permissions of every row above it.
| Role | Sessions | Members | API keys | Audit | Workspace settings |
|---|---|---|---|---|---|
| viewer | list / get | list (self) | list (own) | read | read |
| member | + create / release / keepalive / save-profile | list (all) | list / create / revoke (own) | read | read |
| admin | + force-stop | + invite / role-change / remove | + manage all keys | read | read |
| owner | all | all | all | read | + name / delete |
Underlying scopes (auth/rbac.py): viewer → sessions:read; member → +sessions:write; admin → +admin; owner → +workspace:owner.
A workspace must have at least one owner. The last owner cannot be demoted; the API returns 409 Conflict. System-admin Ventus operators (is_system_admin=true Supabase users) can act on any workspace; every such action is tagged in the audit log with actor_is_system_admin=true.
Invite a member
Owners and admins invite by email from Members → Invite, or POST /v1/dashboard/ws/<slug>/invitations:
curl -X POST https://api.sessions.ventus.ai/api/v1/dashboard/ws/$WS_SLUG/invitations \
-H "Authorization: Bearer $DASHBOARD_SESSION_JWT" \
-d '{"email": "alice@example.com", "role": "member"}'The invitee receives an email with a one-time accept link (https://dashboard.sessions.ventus.ai/auth/callback?token=vsd_inv_…). On click:
- The token is exchanged for membership in the workspace.
- If the email isn't yet a Supabase user, a magic-link sign-in flow
bootstraps them first.
- They land on
/ws/<slug>/sessionsalready-signed-in.
Invitations expire after 7 days. The owner can revoke an unaccepted invitation from the same screen.
Change a role
Owner-only. From Members → ⋯ → Change role or PATCH /v1/dashboard/ws/<slug>/members/<user_id>:
curl -X PATCH https://api.sessions.ventus.ai/api/v1/dashboard/ws/$WS_SLUG/members/$USER_ID \
-H "Authorization: Bearer $DASHBOARD_SESSION_JWT" \
-d '{"role": "owner"}'Demoting the last owner returns 409 Conflict. Removing yourself works if there's another owner.
Remove a member
Owner-only. DELETE /v1/dashboard/ws/<slug>/members/<user_id>. The member loses access immediately; sessions they started keep running and are still attributed to them in the audit log.
Audit-log attribution
The admin_audit_log table records actions performed via the dashboard (human, magic-link-authenticated). Every entry has actor_user_id (NOT NULL), the user's email at the time (actor_email), and an actor_is_system_admin flag.
API-key-driven actions (sessions create/release/keepalive, profiles, etc.) go to the session events / Redis bus, not to admin_audit_log — see the Audit log page for the split.
A removed member's past actions stay attributed to them by actor_user_id — we never re-attribute history. The Supabase user ID is durable across deletion.