{"record":{"id":"050884f58a86983e","repo":"BerriAI/litellm","slug":"cannot-reassign-project-to-a-team-you-are-not-an-a","errorCode":null,"errorMessage":"Cannot reassign project to a team you are not an admin of","messagePattern":"Cannot reassign project to a team you are not an admin of","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"enterprise/litellm_enterprise/proxy/management_endpoints/project_endpoints.py","lineNumber":607,"sourceCode":"            raise HTTPException(\n                status_code=403,\n                detail={\"error\": \"Only admins or team admins can update projects\"},\n            )\n\n        # Reassigning to a different team also requires admin rights on the\n        # destination team — otherwise a team admin could shed projects into\n        # an unsuspecting team's namespace.\n        if data.team_id is not None and data.team_id != existing_project.team_id:\n            can_assign_to_target = await _check_user_permission_for_project(\n                user_api_key_dict=user_api_key_dict,\n                team_id=data.team_id,\n                prisma_client=prisma_client,\n                team_object=(\n                    LiteLLM_TeamTable.model_validate(target_team_obj.model_dump()) if target_team_obj else None\n                ),\n            )\n            if not can_assign_to_target:\n                raise HTTPException(\n                    status_code=403,\n                    detail={\"error\": \"Cannot reassign project to a team you are not an admin of\"},\n                )\n\n        # Validate project limits against team limits\n        if target_team_obj is not None:\n            _check_team_project_limits(\n                team_object=LiteLLM_TeamTable.model_validate(target_team_obj.model_dump()),\n                data=data,\n            )\n\n        # Prepare update data\n        update_data = data.json(exclude_none=True, exclude={\"project_id\"})\n        update_data = prisma_client.jsonify_object(update_data)\n        update_data[\"updated_by\"] = user_api_key_dict.user_id or litellm_proxy_admin_name\n\n        # Handle budget updates\n        budget_fields = LiteLLM_BudgetTable.model_fields.keys()","sourceCodeStart":589,"sourceCodeEnd":625,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/enterprise/litellm_enterprise/proxy/management_endpoints/project_endpoints.py#L589-L625","documentation":"Reassigning a project to a different team (data.team_id != existing_project.team_id) requires, in addition to edit rights on the current team, admin rights on the DESTINATION team — checked via _check_user_permission_for_project against the validated target team object. Without it, HTTP 403. This stops a team admin from dumping projects into another team's namespace.","triggerScenarios":"PUT /project/update including a team_id different from the project's current team, where the caller is not a proxy admin and not an admin of the destination team.","commonSituations":"Org restructures moving projects between teams, team admins 'handing off' a project without coordinating the receiving team's admins.","solutions":["Have a proxy admin or an admin of the destination team perform the reassignment","Add the caller as an admin of the destination team first, then retry","Alternatively, create a new project under the destination team and migrate keys/members deliberately"],"exampleFix":"# before (caller not admin of team B)\ncurl -H 'Authorization: Bearer sk-teamA-admin' -X PUT .../project/update -d '{\"project_id\":\"p1\",\"team_id\":\"team-B\"}'\n# after (proxy admin key)\ncurl -H 'Authorization: Bearer sk-admin' -X PUT .../project/update -d '{\"project_id\":\"p1\",\"team_id\":\"team-B\"}'","handlingStrategy":"validation","validationCode":"proj = await client.get(f'/project/info?project_id={pid}')\ncurrent_team = proj.project.team_id\nif new_team := payload.get('team_id'):\n    if new_team != current_team and my_role != 'proxy_admin':\n        team = await client.get(f'/team/info?team_id={new_team}')\n        admins = [m['user_id'] for m in (team.teams[0].members_with_roles or []) if m.get('role') == 'admin']\n        if me.user_id not in admins:\n            raise PermissionError('Caller is not an admin of the destination team')","typeGuard":"const canReassign = (myRole, myUserId, currentTeamId, targetTeamId, targetTeam) =>\n  targetTeamId === undefined || targetTeamId === currentTeamId ||\n  myRole === 'proxy_admin' ||\n  (targetTeam.members_with_roles ?? []).some((m) => m.user_id === myUserId && m.role === 'admin');","tryCatchPattern":null,"preventionTips":["Treat team reassignment as a two-admin workflow: confirm destination-team admin involvement","Record intended team ownership in your CMDB and validate moves before executing them"],"tags":["litellm","authorization","team","security","http-403"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}