{"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":633,"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 = _jsonified(prisma_client, data.model_dump(exclude_none=True, exclude={\"project_id\"}))\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()\n        budget_updates = {k: v for k, v in update_data.items() if k in budget_fields}","sourceCodeStart":615,"sourceCodeEnd":651,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/enterprise/litellm_enterprise/proxy/management_endpoints/project_endpoints.py#L615-L651","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before (403: team-a admin moves project to team-b)\nhttpx.post(base + '/project/update', headers=team_a_admin_hdr, json={'project_id': 'p1', 'team_id': 'team-b'})\n\n# after: proxy admin performs the move\nhttpx.post(base + '/project/update', headers=proxy_admin_hdr, json={'project_id': 'p1', 'team_id': 'team-b'})","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    r = httpx.post(base + '/project/update', headers=hdr, json=payload)\n    r.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 403 and 'reassign' in e.response.text:\n        raise PermissionError(f'caller is not admin of destination team {payload[\"team_id\"]}; use the proxy admin key for reassignment') from None\n    raise","preventionTips":["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"],"tags":["litellm","rbac","http-403","teams","project-reassignment"],"backgroundTag":"insufficient-permissions","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}