BerriAI/litellm · error · HTTPException
Only admins or team admins can update projects
Error message
Only admins or team admins can update projects
What it means
Update permission is evaluated against the project's CURRENT team (existing_project.team_id), deliberately not the payload's team_id, so a team admin cannot point at another team to pass the check. Callers must be proxy admins or admins of the team that currently owns the project; otherwise 403.
Solutions
- Use the proxy admin master key for updates on projects owned by other teams
- Have an admin of the project's current team perform the update (verify ownership first via GET /project/info)
- If reassignment is the goal, have a proxy admin change team_id first; afterwards the new team's admins can manage it
- Audit ownership before choosing credentials — the payload's team_id grants no rights on update
Example fix
# before (403: team-a admin edits a project now owned by team-b)
httpx.post(base + '/project/update', headers=team_a_admin_hdr, json={'project_id': 'p1', 'project_name': 'x'})
# after: proxy admin key
httpx.post(base + '/project/update', headers=proxy_admin_hdr, json={'project_id': 'p1', 'project_name': 'x'}) 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 'update projects' in e.response.text:
# permission is judged against the project's CURRENT team, not the payload
raise PermissionError('caller is not admin of the team that owns this project; escalate to that team admin or proxy admin') from None
raise Prevention
- Check project ownership (GET /project/info) before choosing credentials for an update
- After reassigning a project, update automation credentials to the new team's admins
- Reserve cross-team edits for the proxy admin credential
When it happens
Trigger: POST /project/update where the key's user is not a proxy admin and not an admin in the team stored on the existing project row — e.g. a team admin editing a project that was reassigned to another team.
Common situations: Projects moved between teams during reorganizations while old team admins keep automation running; shared service keys used across teams; callers assuming the payload's team_id is what authorizes them.
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
- You don't have access to this project
- Cannot reassign project to a team you are not an admin of
- Only admins can delete projects
- 404
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/7050af405c6fa9e8.
Report an issue: GitHub.
Appendix: source
Thrown at enterprise/litellm_enterprise/proxy/management_endpoints/project_endpoints.py:615
)
# Permission to *edit* the project must be evaluated against the
# project's CURRENT team. Sourcing the team from `data.team_id`
# would let an admin of any team pass the check by supplying their
# own team_id, hijacking the project (VERIA-55).
target_team_id = data.team_id or existing_project.team_id
target_team_obj = None
if target_team_id is not None:
target_team_obj = await _validate_team_exists(team_id=target_team_id, prisma_client=prisma_client)
has_permission = await _check_user_permission_for_project(
user_api_key_dict=user_api_key_dict,
team_id=existing_project.team_id,
prisma_client=prisma_client,
)
if not has_permission:
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(View on GitHub (pinned to 77b7c6c40c)