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

  1. Perform the move with the proxy admin master key
  2. Or add the caller as an admin of the destination team first (POST /team/update members_with_roles), then retry the reassignment
  3. 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
  4. 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

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


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)