BerriAI/litellm · error · HTTPException
Cannot reassign project to a team you are not an admin of
Error message
Cannot reassign project to a team you are not an admin of
What it means
Reassigning a project to a different team (data.team_id differing from the existing team) triggers a second permission check evaluated against the destination team, with the team object resolved via _validate_team_exists. This blocks a team admin from dumping a project into another team's namespace; only proxy admins or admins of the destination team may move it.
Solutions
- Perform the move with the proxy admin master key
- Or add the caller as an admin of the destination team first (POST /team/update members_with_roles), then retry the reassignment
- Verify the destination team_id exists and is correct (GET /team/info) — a bad id fails earlier in _validate_team_exists with a team-not-found error
- Encode ownership hand-off rules in your provisioning tooling so reassignment always uses appropriately-scoped credentials
Example fix
# before (403: team-a admin moves project to team-b)
httpx.post(base + '/project/update', headers=team_a_admin_hdr, json={'project_id': 'p1', 'team_id': 'team-b'})
# after: proxy admin performs the move
httpx.post(base + '/project/update', headers=proxy_admin_hdr, json={'project_id': 'p1', 'team_id': 'team-b'}) Defensive patterns
Strategy: try-catch
Try / catch
try:
r = httpx.post(base + '/project/update', headers=hdr, json=payload)
r.raise_for_status()
except httpx.HTTPStatusError as e:
if e.response.status_code == 403 and 'reassign' in e.response.text:
raise PermissionError(f'caller is not admin of destination team {payload["team_id"]}; use the proxy admin key for reassignment') from None
raise Prevention
- Perform cross-team moves exclusively with the proxy admin credential
- Verify the destination team_id exists and is intended (GET /team/info) before reassigning
- Model hand-offs as explicit two-step approvals rather than one admin reshuffling everything
When it happens
Trigger: POST /project/update with team_id set to a team different from the project's current team, where the caller is not an admin of that destination team.
Common situations: Reorganizations moving projects between teams with a single team-admin credential; typo'd destination team ids; automation that 'hands off' projects without involving the receiving team's admins.
Understand the failure class
Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.
Related errors
- Only admins or team admins can create projects. Your role is
- Only admins or team admins can update projects
- You don't have access to this project
- Only admins can delete projects
- 404
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/050884f58a86983e.
Report an issue: GitHub.
Appendix: source
Thrown at enterprise/litellm_enterprise/proxy/management_endpoints/project_endpoints.py:633
raise HTTPException(
status_code=403,
detail={"error": "Only admins or team admins can update projects"},
)
# Reassigning to a different team also requires admin rights on the
# destination team — otherwise a team admin could shed projects into
# an unsuspecting team's namespace.
if data.team_id is not None and data.team_id != existing_project.team_id:
can_assign_to_target = await _check_user_permission_for_project(
user_api_key_dict=user_api_key_dict,
team_id=data.team_id,
prisma_client=prisma_client,
team_object=(
LiteLLM_TeamTable.model_validate(target_team_obj.model_dump()) if target_team_obj else None
),
)
if not can_assign_to_target:
raise HTTPException(
status_code=403,
detail={"error": "Cannot reassign project to a team you are not an admin of"},
)
# Validate project limits against team limits
if target_team_obj is not None:
_check_team_project_limits(
team_object=LiteLLM_TeamTable.model_validate(target_team_obj.model_dump()),
data=data,
)
# Prepare update data
update_data = _jsonified(prisma_client, data.model_dump(exclude_none=True, exclude={"project_id"}))
update_data["updated_by"] = user_api_key_dict.user_id or litellm_proxy_admin_name
# Handle budget updates
budget_fields = LiteLLM_BudgetTable.model_fields.keys()
budget_updates = {k: v for k, v in update_data.items() if k in budget_fields}View on GitHub (pinned to 77b7c6c40c)