stack/docs/templates-python/concepts/teams-management.mdx
Madison 22235127b9
Docs updates (#753)
<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md

-->

<!-- ELLIPSIS_HIDDEN -->


----

> [!IMPORTANT]
> This pull request updates the Stack Auth documentation structure,
enhances navigation and layout functionalities, and introduces new
components for improved user experience.
> 
>   - **Behavior**:
> - Introduces `PlatformRedirect` component in `platform-redirect.tsx`
for redirecting users to their preferred platform.
> - Adds `usePlatformPreference` hook in `use-platform-preference.ts`
for managing platform preferences.
> - Updates `getSmartRedirectUrl()` in `navigation-utils.ts` to use
`getSmartPlatformRedirect()`.
>   - **Layout and Navigation**:
> - Enhances sidebar functionality with collapsible sections in
`docs.tsx` and `sidebar-context.tsx`.
> - Adds `DocsSidebarCollapseTrigger` in `docs.tsx` for sidebar
collapse/expand functionality.
> - Updates `SharedHeader` in `shared-header.tsx` to include
platform-aware navigation links.
>   - **Documentation Structure**:
> - Updates `meta.json` files in `templates` to reflect new
documentation structure.
> - Renames `overview.mdx` to `index.mdx` in `sdk` and `components`
directories.
> - Adds detailed documentation for `Team`, `TeamUser`, and
`ContactChannel` in respective `.mdx` files.
> 
> <sup>This description was created by </sup>[<img alt="Ellipsis"
src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=stack-auth%2Fstack-auth&utm_source=github&utm_medium=referral)<sup>
for 21e55737cb. You can
[customize](https://app.ellipsis.dev/stack-auth/settings/summaries) this
summary. It will automatically update as commits are pushed.</sup>


<!-- ELLIPSIS_HIDDEN -->

---------

Co-authored-by: Stack-Bot <madison@stack-auth.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: Konsti Wohlwend <n2d4xc@gmail.com>
2025-07-11 10:00:02 -07:00

600 lines
17 KiB
Plaintext

---
title: "Teams Management"
description: "Learn how to implement team functionality in your Python application using Stack Auth's REST API"
---
After setting up your [Stack Auth helper function](../getting-started/setup.mdx), you can implement comprehensive team functionality in your Python application.
## Team Management
Stack Auth provides full team management capabilities including:
- **Team Creation & Management** - Create and update teams with metadata
- **Team Memberships** - Add and remove users from teams
- **Team Invitations** - Send email invitations to join teams
- **Team Permissions** - Control what team members can do
- **Team Profiles** - Manage user profiles within team context
### Creating a Team
To create a new team:
```python
def create_team(access_token, display_name, creator_user_id=None):
"""
Create a new team
Returns the created team data
"""
body = {
'display_name': display_name
}
# Optionally specify a creator (only on server)
if creator_user_id:
body['creator_user_id'] = creator_user_id
response = stack_auth_request('POST', 'api/v1/teams',
headers={'x-stack-access-token': access_token},
json=body
)
return {
'id': response['id'],
'display_name': response['display_name'],
'profile_image_url': response['profile_image_url'],
'client_metadata': response['client_metadata'],
'client_read_only_metadata': response['client_read_only_metadata'],
'created_at_millis': response.get('created_at_millis') # Server only
}
# Example usage
team_data = create_team(
access_token=access_token,
display_name="Engineering Team"
)
team_id = team_data['id']
print(f"Created team: {team_data['display_name']}")
```
### Listing Teams
Get all teams for the current user:
```python
def list_user_teams(access_token):
"""
List all teams that the current user is a member of
"""
response = stack_auth_request('GET', 'api/v1/teams?user_id=me',
headers={'x-stack-access-token': access_token}
)
return response['items']
def list_all_teams():
"""
List all teams in the project (server access only)
"""
response = stack_auth_request('GET', 'api/v1/teams')
return response['items']
# Example usage
user_teams = list_user_teams(access_token)
print(f"User is member of {len(user_teams)} teams")
# Server-side: list all teams
all_teams = list_all_teams()
print(f"Total teams in project: {len(all_teams)}")
```
### Getting Team Information
Retrieve details about a specific team:
```python
def get_team(access_token, team_id):
"""
Get information about a specific team
"""
response = stack_auth_request('GET', f'api/v1/teams/{team_id}',
headers={'x-stack-access-token': access_token}
)
return {
'id': response['id'],
'display_name': response['display_name'],
'profile_image_url': response['profile_image_url'],
'client_metadata': response['client_metadata'],
'client_read_only_metadata': response['client_read_only_metadata']
}
# Example usage
team_info = get_team(access_token, team_id)
print(f"Team: {team_info['display_name']}")
```
### Updating Team Information
Update team details (requires `$update_team` permission):
```python
def update_team(access_token, team_id, **updates):
"""
Update team information
Requires $update_team permission
"""
# Filter out None values
body = {k: v for k, v in updates.items() if v is not None}
response = stack_auth_request('PATCH', f'api/v1/teams/{team_id}',
headers={'x-stack-access-token': access_token},
json=body
)
return response
# Example usage
updated_team = update_team(
access_token=access_token,
team_id=team_id,
display_name="Updated Engineering Team",
profile_image_url="https://example.com/team-logo.png",
client_metadata={
"department": "Engineering",
"location": "San Francisco"
}
)
```
## Team Membership Management
### Adding Members to a Team
Add users to a team (server access required):
```python
def add_team_member(team_id, user_id):
"""
Add a user to a team (server access only)
"""
response = stack_auth_request('POST', f'api/v1/team-memberships/{team_id}/{user_id}',
json={}
)
return response
# Example usage
add_team_member(team_id, user_id)
print(f"Added user {user_id} to team {team_id}")
```
### Removing Members from a Team
Remove users from a team (requires `$remove_members` permission):
```python
def remove_team_member(access_token, team_id, user_id):
"""
Remove a user from a team
Requires $remove_members permission
"""
stack_auth_request('DELETE', f'api/v1/team-memberships/{team_id}/{user_id}',
headers={'x-stack-access-token': access_token}
)
# Example usage
remove_team_member(access_token, team_id, user_id)
print(f"Removed user {user_id} from team {team_id}")
```
### Getting Team Members
List all members of a team:
```python
def get_team_members(access_token, team_id):
"""
Get all members of a team with their profiles
Requires $read_members permission
"""
response = stack_auth_request('GET', f'api/v1/team-member-profiles?team_id={team_id}',
headers={'x-stack-access-token': access_token}
)
return response['items']
# Example usage
members = get_team_members(access_token, team_id)
for member in members:
print(f"Member: {member['display_name']} ({member['user_id']})")
```
## Team Invitations
### Sending Team Invitations
Invite users to join a team via email:
```python
def send_team_invitation(access_token, team_id, email, callback_url):
"""
Send an invitation to join a team
Requires $invite_members permission
"""
response = stack_auth_request('POST', 'api/v1/team-invitations/send-code',
headers={'x-stack-access-token': access_token},
json={
'email': email,
'team_id': team_id,
'callback_url': callback_url
}
)
return {
'success': response['success'],
'invitation_id': response['id']
}
# Example usage
invitation_result = send_team_invitation(
access_token=access_token,
team_id=team_id,
email="newmember@example.com",
callback_url="https://yourapp.com/join-team"
)
print(f"Invitation sent: {invitation_result['invitation_id']}")
```
### Accepting Team Invitations
Complete the invitation process when a user clicks the invitation link:
```python
def accept_team_invitation(code):
"""
Accept a team invitation using the code from the invitation email
"""
response = stack_auth_request('POST', 'api/v1/team-invitations/accept',
json={'code': code}
)
return response
# Example usage (when user clicks invitation link)
accept_team_invitation(invitation_code)
print("User successfully joined the team!")
```
### Listing Team Invitations
Get pending invitations for a team:
```python
def list_team_invitations(access_token, team_id):
"""
List pending invitations for a team
Requires $invite_members permission
"""
response = stack_auth_request('GET', f'api/v1/team-invitations?team_id={team_id}',
headers={'x-stack-access-token': access_token}
)
return response['items']
# Example usage
invitations = list_team_invitations(access_token, team_id)
for invitation in invitations:
print(f"Pending invitation for: {invitation['recipient_email']}")
```
## Team Permissions Management
### Granting Team Permissions
Give specific permissions to team members:
```python
def grant_team_permission(team_id, user_id, permission_id):
"""
Grant a permission to a user in a team (server access only)
"""
response = stack_auth_request('POST', f'api/v1/team-permissions/{team_id}/{user_id}/{permission_id}',
json={}
)
return response
# Example usage
grant_team_permission(team_id, user_id, "$update_team")
grant_team_permission(team_id, user_id, "$invite_members")
print(f"Granted permissions to user {user_id}")
```
### Revoking Team Permissions
Remove permissions from team members:
```python
def revoke_team_permission(team_id, user_id, permission_id):
"""
Revoke a permission from a user in a team (server access only)
"""
stack_auth_request('DELETE', f'api/v1/team-permissions/{team_id}/{user_id}/{permission_id}')
# Example usage
revoke_team_permission(team_id, user_id, "$update_team")
print(f"Revoked permission from user {user_id}")
```
### Checking Team Permissions
Check if a user has specific permissions in a team:
```python
def check_team_permission(access_token, team_id, user_id, permission_id):
"""
Check if a user has a specific permission in a team
"""
try:
response = stack_auth_request('GET', f'api/v1/team-permissions/{team_id}/{user_id}/{permission_id}',
headers={'x-stack-access-token': access_token}
)
return True
except Exception as e:
if "TEAM_PERMISSION_NOT_FOUND" in str(e):
return False
raise e
# Example usage
can_update = check_team_permission(access_token, team_id, user_id, "$update_team")
if can_update:
print("User can update the team")
else:
print("User cannot update the team")
```
### Listing User Permissions
Get all permissions a user has in a team:
```python
def list_user_team_permissions(access_token, team_id, user_id="me"):
"""
List all permissions a user has in a team
"""
response = stack_auth_request('GET', f'api/v1/team-permissions/{team_id}/{user_id}',
headers={'x-stack-access-token': access_token}
)
return response['items']
# Example usage
permissions = list_user_team_permissions(access_token, team_id)
permission_ids = [p['id'] for p in permissions]
print(f"User permissions: {permission_ids}")
```
## Team Member Profiles
### Managing Team Member Profiles
Users can have different display names and profile information within each team:
```python
def update_team_member_profile(access_token, team_id, user_id="me", **profile_data):
"""
Update a user's profile within a team context
"""
response = stack_auth_request('PATCH', f'api/v1/team-member-profiles/{team_id}/{user_id}',
headers={'x-stack-access-token': access_token},
json=profile_data
)
return response
def get_team_member_profile(access_token, team_id, user_id="me"):
"""
Get a user's profile within a team context
"""
response = stack_auth_request('GET', f'api/v1/team-member-profiles/{team_id}/{user_id}',
headers={'x-stack-access-token': access_token}
)
return response
# Example usage
# Update current user's profile in the team
updated_profile = update_team_member_profile(
access_token=access_token,
team_id=team_id,
display_name="John Doe (Engineering Lead)",
profile_image_url="https://example.com/john-avatar.png"
)
# Get the updated profile
profile = get_team_member_profile(access_token, team_id)
print(f"Team profile: {profile['display_name']}")
```
### Deleting Teams
Remove a team entirely (requires `$delete_team` permission):
```python
def delete_team(access_token, team_id):
"""
Delete a team (requires $delete_team permission)
"""
response = stack_auth_request('DELETE', f'api/v1/teams/{team_id}',
headers={'x-stack-access-token': access_token}
)
return response
# Example usage
delete_team(access_token, team_id)
print(f"Team {team_id} deleted successfully")
```
## Complete Team Management Example
Here's a comprehensive example that demonstrates a full team management workflow:
```python
import os
import requests
# Setup (from setup guide)
stack_project_id = os.getenv("STACK_PROJECT_ID")
stack_publishable_client_key = os.getenv("STACK_PUBLISHABLE_CLIENT_KEY")
stack_secret_server_key = os.getenv("STACK_SECRET_SERVER_KEY")
def stack_auth_request(method, endpoint, **kwargs):
res = requests.request(
method,
f'https://api.stack-auth.com/{endpoint}',
headers={
'x-stack-access-type': 'server', # or 'client' if you're only accessing the client API
'x-stack-project-id': stack_project_id,
'x-stack-publishable-client-key': stack_publishable_client_key,
'x-stack-secret-server-key': stack_secret_server_key, # not necessary if access type is 'client'
**kwargs.pop('headers', {}),
},
**kwargs,
)
if res.status_code >= 400:
raise Exception(f"Stack Auth API request failed with {res.status_code}: {res.text}")
return res.json()
class TeamManager:
def __init__(self, access_token):
self.access_token = access_token
def create_and_setup_team(self, name, member_emails):
"""Create a team and invite members"""
# Create the team
team = create_team(self.access_token, name)
team_id = team['id']
print(f"Created team: {name} (ID: {team_id})")
# Send invitations to members
invitation_results = []
for email in member_emails:
try:
result = send_team_invitation(
self.access_token,
team_id,
email,
"https://yourapp.com/join-team"
)
invitation_results.append((email, result['invitation_id']))
print(f"Invited {email}")
except Exception as e:
print(f"Failed to invite {email}: {e}")
return team, invitation_results
def manage_team_permissions(self, team_id, admin_user_ids):
"""Grant admin permissions to specific users"""
admin_permissions = ["$update_team", "$invite_members", "$remove_members"]
for user_id in admin_user_ids:
for permission in admin_permissions:
try:
grant_team_permission(team_id, user_id, permission)
print(f"Granted {permission} to {user_id}")
except Exception as e:
print(f"Failed to grant {permission} to {user_id}: {e}")
def get_team_overview(self, team_id):
"""Get complete team information"""
# Get team details
team_info = get_team(self.access_token, team_id)
# Get team members
try:
members = get_team_members(self.access_token, team_id)
except Exception:
members = [] # User might not have read_members permission
# Get pending invitations
try:
invitations = list_team_invitations(self.access_token, team_id)
except Exception:
invitations = [] # User might not have invite_members permission
return {
'team': team_info,
'members': members,
'pending_invitations': invitations
}
# Example usage
team_manager = TeamManager(access_token)
# Create a new team with initial members
team, invitations = team_manager.create_and_setup_team(
name="Product Team",
member_emails=["alice@example.com", "bob@example.com", "charlie@example.com"]
)
# Make some users team admins (server-side operation)
admin_users = ["user-id-1", "user-id-2"]
team_manager.manage_team_permissions(team['id'], admin_users)
# Get team overview
overview = team_manager.get_team_overview(team['id'])
print(f"\nTeam Overview:")
print(f"Name: {overview['team']['display_name']}")
print(f"Members: {len(overview['members'])}")
print(f"Pending Invitations: {len(overview['pending_invitations'])}")
```
## Error Handling
Common team-related errors you might encounter:
```python
def handle_team_errors(func):
"""Decorator to handle common team operation errors"""
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
error_message = str(e)
if "TEAM_PERMISSION_REQUIRED" in error_message:
print("Insufficient permissions for this team operation")
elif "TEAM_MEMBERSHIP_NOT_FOUND" in error_message:
print("User is not a member of this team")
elif "TEAM_NOT_FOUND" in error_message:
print("Team not found")
elif "TEAM_MEMBERSHIP_ALREADY_EXISTS" in error_message:
print("User is already a member of this team")
elif "USER_NOT_FOUND" in error_message:
print("User not found")
else:
print(f"Team operation error: {error_message}")
raise e
return wrapper
@handle_team_errors
def safe_add_member(team_id, user_id):
return add_team_member(team_id, user_id)
@handle_team_errors
def safe_send_invitation(access_token, team_id, email, callback_url):
return send_team_invitation(access_token, team_id, email, callback_url)
```
## Common Team Permission IDs
Stack Auth includes several built-in team permissions:
- **`$update_team`** - Edit team information and metadata
- **`$delete_team`** - Delete the entire team
- **`$invite_members`** - Send invitations to new members
- **`$remove_members`** - Remove members from the team
- **`$read_members`** - View team member list
- **`team_member`** - Basic team membership (automatically granted)
For more advanced team features, check out the [REST API documentation](../rest-api/overview.mdx).