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

  1. Use the proxy admin master key for updates on projects owned by other teams
  2. Have an admin of the project's current team perform the update (verify ownership first via GET /project/info)
  3. If reassignment is the goal, have a proxy admin change team_id first; afterwards the new team's admins can manage it
  4. 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

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


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)