Teams & Members
Create teams, manage member roles, authorize models, and transfer ownership
A team is Clouisle's resource isolation boundary: agents, workflows, knowledge bases, tools, and skills all belong to a team. Every in-team operation must pass two layers — a global permission code and a team membership role gate.
This page covers creating teams, adding/changing/removing members, leaving, transferring ownership, and deleting a team, plus the exact failure reason for each step. For the full role model see Roles & Permissions and the Permissions & Roles Reference.
Create a Team
Creating a team requires the global admin:team:create permission and happens on the dashboard Teams page (/teams). Members and Viewers cannot see that entry (/teams requires admin:team:read).
- Go to Admin → Teams.
- Click Create Team and fill in:
- Team Name (required, globally unique; a duplicate returns
team_name_exists) - Description (optional)
- Team Avatar (optional, uploaded from your machine; JPEG/PNG/GIF/WebP/SVG/ICO, recommended 200×200, max 10 MB; the upload result is stored in
avatar_url)
- Team Name (required, globally unique; a duplicate returns
- Save; this calls
POST /api/v1/admin/teams. - The creator automatically becomes the team's owner (a
TeamMemberrow withrole=owneris written too).
Not implemented / Roadmap: assigning another owner at creation, adding initial members, invite/join settings, and resource quotas are not part of the creation flow. Members join afterwards via Add Member; ownership moves via Transfer Ownership.
After creating it, select it in the team switcher. Confirm the current team when creating new agents, workflows, and knowledge bases — otherwise the resource lands in the wrong team and can only be fixed by cross-team sharing (tools only) or by recreating it.

The Two Team Surfaces
The team switcher exposes two entries with completely different permissions and capabilities:
| Entry | Route | Requires | Purpose |
|---|---|---|---|
| Manage All Teams (Admin) | /teams | global admin:team:read | List every team (paginated, searchable by name/description, page_size=50 by default), create, edit, delete, bulk delete; backend is GET/POST/DELETE /api/v1/admin/teams |
| Manage Current Team | /app/team | the page itself only requires membership (team:read); the entry link appears only for a superuser or a team owner/admin holding team:manage | Member management, basic team info editing, leaving, transferring ownership, viewing authorized models (read-only) |
The model authorization tab on /app/team is read-only: it shows the team's authorized models, enabled state, and today's usage/limit, and states that authorization is assigned by a system administrator. Every model-authorization write requires a superuser (see below).
Team Details and Members
Team Overview
The /app/team header shows the team avatar, name, a Default Team badge (is_default), and a badge with your role inside this team. The members tab lists username, email, role, and join date, with local filtering by username/email.
Not implemented / Roadmap: team details do not show resource quotas (agent/workflow/storage limits) or usage activity (conversations, runs, API calls), and there is no per-team notification setting or separate resources tab.
Model Authorization
Model authorization is a superuser-only capability; every write endpoint requires get_current_active_superuser:
| Operation | Endpoint | Permission |
|---|---|---|
| List the team's authorized models | GET /api/v1/teams/{team_id}/models | team membership (or superuser) |
| Authorize a model | POST /api/v1/teams/{team_id}/models | superuser |
| Bulk authorize | POST /api/v1/teams/{team_id}/models/batch | superuser |
| Change quota / enabled state / priority | PUT /api/v1/teams/{team_id}/models/{model_id} | superuser |
| Revoke an authorization | DELETE /api/v1/teams/{team_id}/models/{model_id} | superuser |
| Bulk revoke | DELETE /api/v1/teams/{team_id}/models/batch | superuser |
| List models available to authorize | GET /api/v1/teams/{team_id}/available-models | superuser |
The dashboard team detail's Model Authorization tab offers the same operations, with buttons gated on is_superuser || admin:model:update. Configurable fields include daily/monthly token limits, daily/monthly request limits, is_enabled, and priority. Granting or revoking sends a team.model_granted / team.model_revoked notification to the team. See Manage team models for details.
Add Members
Requires the global team:manage permission and owner/admin inside that team (require_team_admin=True). Members and Viewers get 403 team_admin_required.
- Open the Members tab of
/app/teamand click Add Member (shown only when you can manage the team). - In the "username or email" field, enter the target user's complete username or email.
- Pick a role: Admin, Member, or Viewer (Owner is not offered).
- Confirm; this calls
POST /api/v1/teams/{team_id}/members.
Matching rules and backend validation:
| Case | Result |
|---|---|
| Exact username match, or case-insensitive exact email match | user found |
| Nothing matches | user_not_found, HTTP 404 |
Only a fragment (e.g. the prefix john) | no match, 404 — there is no fuzzy search or user picker |
| The user is already a member | already_team_member |
Role set to owner | cannot_add_as_owner |
Role set to admin but the target lacks global team:manage | operation_not_permitted, HTTP 403 (permission: team:manage) |
Grant the global role before making someone a team admin
Adding a user who lacks global team:manage as a team admin is rejected outright. The order must be: first grant them the Team Admin role (or any role with team:manage) in Admin → Users, then set them as team admin.
On success the new member receives a team.member_added notification and the team gets a summary notification; membership is effective immediately — there is no invitation/accept flow.
Change Member Roles
Only the team owner (or a superuser) can change roles. A team admin gets 403 team_owner_required.
- Find the target member in the list and open the ... menu.
- Choose Change Role and pick Admin / Member / Viewer.
- Save; this calls
PUT /api/v1/teams/{team_id}/members/{user_id}.
Backend validation order:
| Case | Result |
|---|---|
| Caller is not the owner (and not a superuser) | team_owner_required, HTTP 403 |
Target is currently owner | cannot_change_owner_role — the owner's role cannot be modified; use Transfer Ownership |
New role is owner | cannot_promote_to_owner — promoting to owner only happens via Transfer Ownership |
New role is admin but the target lacks global team:manage | operation_not_permitted, HTTP 403 |
| Target is not a member of the team | team_member_not_found, HTTP 404 |
On success the target receives a team.role_changed notification.
Member role changes are not audited: team audit coverage is limited to create_team / update_team / delete_team / add_team_member / remove_team_member plus the team model authorization actions.
The frontend hides the menu as well: canActOnMember requires "you are not the target" and "you are the owner, or the target is not an admin". So a team admin never sees the action menu for other admins or the owner, and nobody sees it on their own row.
Remove Members
DELETE /api/v1/teams/{team_id}/members/{user_id} has two paths:
| Scenario | Required permission | Notes |
|---|---|---|
| Remove someone else | global team:manage + team owner/admin | the frontend menu is likewise limited by canActOnMember |
| Remove yourself | team:read only | self-removal needs no management permission (equivalent to leaving) |
Shared limits: an owner can never be removed (cannot_remove_owner, including by themselves); if the target is not a member you get team_member_not_found (404).
After removal the user immediately loses access to all of that team's resources; only the membership row is deleted, so their conversations, API keys, and personal resources are not deleted (conversations simply become inaccessible without team access). The team receives a team.member_removed notification and so does the removed user. They can be added again later.
Leaving Teams
/app/team→ Settings tab → danger zone, click Leave Team.- Confirm in the dialog; this calls
POST /api/v1/teams/{team_id}/leave(requires onlyteam:read).
An owner cannot leave: the endpoint returns owner_cannot_leave, and the frontend shows "As the team owner, you cannot directly leave the team. Please transfer ownership first." You must transfer ownership to leave. Also, the default team (is_default=true) hides the leave button in the UI — but that is frontend behaviour; the endpoint itself does not reject on is_default.
After leaving you lose access to that team's resources; existing conversations are preserved. The team list refreshes and the page returns to /app.
Transfer Ownership
Requires global team:manage and owner/admin inside the team, and you must be the owner (or a superuser). A team admin gets 403 team_owner_required.
- Open the member list on
/app/team, find the target member, ... → Transfer Ownership (only owners see this item). - Confirm in the dialog; this calls
POST /api/v1/teams/{team_id}/transfer-ownership?new_owner_id={user_id}.
Backend rules (in order):
- The target must be a member of the team, otherwise
team_member_not_found(404). - The target must already hold global
team:manage, otherwiseoperation_not_permitted(403). Again: grant the global role first. - If the target is the current owner →
cannot_promote_to_owner(400). - The previous owner's membership role is demoted to
admin, the target becomesowner, andTeam.owneris updated accordingly. - Both owners receive a
team.ownership_transferrednotification.
Transferring is instant and irreversible from your side: the former owner keeps only admin rights and cannot revert it themselves (changing roles requires being the owner). Confirm the target before proceeding.
Edit Team Info
Requires the global team:update permission and owner/admin inside the team (PUT /api/v1/teams/{team_id}).
/app/team→ Settings tab: change team name, description, and avatar (inputs are disabled when you lack the permission).- The dashboard
/teamsedit dialog: the same fields, requiringadmin:team:update.
Fields and constraints: name is required and globally unique (duplicate → team_name_exists); description is optional; avatar_url is written by the upload component. There is no team slug, alias, or domain field.
Delete a Team
Deleting a team is irreversible and only possible from the dashboard /teams, requiring admin:team:delete. A team owner has no delete entry on /app/team.
- In the
/teamslist open the target team's ... menu → Delete Team (the default team does not show the item). - Confirm in the dialog; this calls
DELETE /api/v1/admin/teams/{team_id}. The dialog only asks for confirmation — you do not type the team name. - The default team (
is_default=true) is rejected:cannot_delete_default_team.
Deletion impact (model FK cascades, verified in models/*.py):
| Deleted | Preserved |
|---|---|
The team record and all TeamMember rows | Members' user accounts |
| The team's agents, workflows, knowledge bases, tools, skills, assets | Audit logs (for compliance) |
Team model authorizations (TeamModel), tool configs, notifications, import sessions | Members' account data (API keys belong to the user, not the team) |
| Agent conversations and their messages (cascading with the agents) | — |
Team Roles and Permissions
A team role is one of four fixed values — owner / admin / member / viewer — and carries no permission codes; it only acts as the team gate. Actual capability = global role permissions ∩ team gate.
Gate Rules
TEAM_MANAGEMENT_PERMISSIONS=team:update,team:manage,team:delete,tool:delete: these actions require both the global permission code andowner/adminin that team.- A team
viewermay only useteam:read,agent:read/agent:chat,workflow:read/workflow:run/workflow:execute,kb:read/kb:test,tool:read/tool:execute,skill:read/skill:execute,conversation:read; every other write returns 403operation_not_permitted. - A superuser (
is_superuser) bypasses membership and the team gate entirely.
Team Management Permissions
| Action | Owner | Team Admin | Member | Viewer |
|---|---|---|---|---|
| View team and members | ✓ | ✓ | ✓ | ✓ |
| Edit team info (name/description/avatar) | ✓ | ✓ | ✗ | ✗ |
| Add members | ✓ | ✓ | ✗ | ✗ |
| Change member roles | ✓ | ✗ | ✗ | ✗ |
| Remove members | ✓ | ✓ (not other admins/owner) | ✗ | ✗ |
| Leave the team | ✗ (transfer first) | ✓ | ✓ | ✓ |
| Transfer ownership | ✓ | ✗ | ✗ | ✗ |
| Delete the team | ✗ (needs dashboard admin:team:delete) | ✗ | ✗ | ✗ |
In-Team Resource Permissions
| Action | Owner | Team Admin | Member | Viewer |
|---|---|---|---|---|
| Create agents / workflows / knowledge bases | ✓ | ✓ | ✓ | ✗ |
| Edit others' agents / workflows / knowledge bases | ✓ | ✓ | ✗ | ✗ |
| Delete agents, publish agents and workflows | ✓ | ✓ | ✗ | ✗ |
| Chat with agents, run workflows | ✓ | ✓ | ✓ | ✓ |
| Create/edit your own tools and skills | ✓ | ✓ | ✓ | ✗ |
| Edit others' tools / skills | ✓ | ✓ | ✗ | ✗ |
| Delete tools | ✓ | ✓ | ✗ (team gate rejects, even though Member holds tool:delete) | ✗ |
| Search/test knowledge bases, upload/rename/delete documents | ✓ | ✓ | ✓ | read-only search |
| Execute tools and skills | ✓ | ✓ | ✓ | ✓ |
API keys are not part of a team
An API key belongs to a person via user_id: for non-superusers the list endpoint only returns their own keys, and ensure_api_key_owner() requires "owner or superuser". Team roles have no effect on API keys — a team owner/admin cannot see or revoke anyone else's key.
Default Team Role
The site setting default_team_id names the default team and default_team_role names the membership role new users get there. It accepts only viewer / member / admin (any other value falls back to member, which is also the default value). Assignment runs on registration and on SSO auto-provisioning, and never overwrites an existing membership. owner is never assigned automatically.
Custom Team Roles
Not implemented. Teams have exactly four fixed roles —
owner,admin,member,viewer— each mapping to a fixed set of gate rules; you cannot add or remove permissions for a single team. For finer-grained permissions, use global custom roles instead (see Roles & Permissions).
Collaboration and Notifications
Collaboration happens through shared resources: agents, workflows, knowledge bases, tools, and skills are shared inside the team, and members access them subject to the role gate. Workflow run history is visible to team members (permissions permitting), so you can see who ran what and when.
Team-related automatic notifications (delivery channels are configured by administrators in site settings — see Notifications & External Channels):
| Type | When | Recipients |
|---|---|---|
team.member_added | A member is added | the new member + the team |
team.member_removed | A member is removed | the removed user + the team |
team.role_changed | A member's role changes | the affected member |
team.ownership_transferred | Ownership is transferred | one each to the old and new owner |
team.model_granted / team.model_revoked | A model is authorized / revoked | the team |
Not implemented / Roadmap: there is no per-team notification configuration, and no @mentions, shared conversations, team activity feed, document comments, or in-app team messaging.
Troubleshooting
Cannot Access Team Resources
- Confirm the correct team is selected in the team switcher.
- Confirm you are a member of that team (if
/app/teamopens you are; otherwisenot_team_member). - Confirm your global roles carry the needed code; a team
viewercan only read and execute. - Confirm resource
visibility: aprivateresource returnsagent_access_denied/tool_access_deniedto non-creators.
Cannot Change Role / No "Change Role" in the Menu
- Only the owner (or a superuser) can change roles; a team admin gets
team_owner_required. - The frontend also hides it: you cannot act on yourself, and a team admin cannot see the menu for other admins or the owner.
- An owner's role cannot be changed (
cannot_change_owner_role); to change who owns the team, use Transfer Ownership. - To promote someone to team
admin, they must already hold globalteam:manage.
Adding a Member Returns 404 user_not_found
The input must be the complete username or complete email (email is case-insensitive). Fragments, nicknames, and display names do not match — there is no fuzzy search.
Adding a Member Returns 403 operation_not_permitted
The role was admin but the target lacks global team:manage. Grant the Team Admin global role first, then set them as team admin.
Ownership Transfer Rejected
team_owner_required: you are not the owner (or a superuser).team_member_not_found: the target is not a team member; add them first.operation_not_permitted: the target lacks globalteam:manage.cannot_promote_to_owner: the target is already the owner.
Cannot Leave / No Leave Button
An owner cannot leave (owner_cannot_leave), and the default team hides the button in the UI. Transfer ownership first; for the default team an administrator must change the default team in site settings.
Cannot Delete a Team
- Requires
admin:team:delete; the entry exists only on the dashboard/teams. - The default team is rejected:
cannot_delete_default_team.
Permissions Unchanged After a Role Change
The frontend's cached permission snapshot may be stale: refresh the page or sign in again. The backend reads permissions from the database per request, so no cache expiry wait is needed.
API Endpoints
Team endpoints use JWT user-session authentication (API keys are not accepted).
Platform side (/api/v1/teams):
| Method | Path | Purpose | Permission / gate |
|---|---|---|---|
| GET | /api/v1/teams/my | List the current user's teams and roles | any signed-in user |
| GET | /api/v1/teams/{team_id} | Team details (includes the member list) | team:read + membership |
| PUT | /api/v1/teams/{team_id} | Change name/description/avatar | team:update + owner/admin |
| POST | /api/v1/teams/{team_id}/members | Add a member (identifier or user_id + role) | team:manage + owner/admin |
| PUT | /api/v1/teams/{team_id}/members/{user_id} | Change a member's role | team:manage + owner/admin, and the caller must be owner/superuser |
| DELETE | /api/v1/teams/{team_id}/members/{user_id} | Remove a member (self = leave) | others: team:manage + owner/admin; self: team:read |
| POST | /api/v1/teams/{team_id}/leave | Leave the team | team:read; owners are rejected |
| POST | /api/v1/teams/{team_id}/transfer-ownership?new_owner_id=… | Transfer ownership | team:manage + owner/superuser; the target must hold global team:manage |
Admin side (/api/v1/admin/teams):
| Method | Path | Purpose | Permission |
|---|---|---|---|
| GET | /api/v1/admin/teams | List all teams (page/page_size, default 50; search matches name or description) | admin:team:read |
| POST | /api/v1/admin/teams | Create a team (creator becomes owner) | admin:team:create |
| DELETE | /api/v1/admin/teams/{team_id} | Delete a team | admin:team:delete; the default team is rejected |
There is no GET /api/v1/teams (list all teams) endpoint. To walk every team in the system, use the admin-side GET /api/v1/admin/teams.
Common Error Codes
msg_key | HTTP | Meaning and fix |
|---|---|---|
team_not_found | 404 | The team does not exist |
not_team_member | 403 | The caller is not a member of that team |
team_admin_required | 403 | Team owner/admin required |
team_owner_required | 403 | Only the team owner (or a superuser) may do this |
operation_not_permitted | 403 | Missing global permission code (the response carries a permission field) |
team_name_exists | 400 | Duplicate team name |
user_not_found | 404 | The member identifier matched no user |
already_team_member | 400 | The user is already a team member |
cannot_add_as_owner | 400 | Cannot set owner through Add Member |
cannot_change_owner_role | 400 | The owner's membership role cannot be changed |
cannot_promote_to_owner | 400 | Cannot promote to owner via a role change or by transferring to yourself |
cannot_remove_owner | 400 | The owner cannot be removed (including self-removal) |
owner_cannot_leave | 400 | The owner must transfer ownership before leaving |
team_member_not_found | 404 | The target user is not a member of that team |
cannot_delete_default_team | 400 | The default team cannot be deleted |
team_model_already_authorized | 400 | That model is already authorized for this team |
team_model_not_found | 404 | The team model authorization does not exist |
For complete request/response formats and code examples, see Teams API.
See also:
- Roles & Permissions — global roles, team gates, and 403 troubleshooting
- User Management — user accounts and global role assignment
- Manage Team Models — model authorization and quotas
How is this guide?