{"record":{"id":"fc12360b3f700f72","repo":"BerriAI/litellm","slug":"user-key-user-id-is-not-a-member-of-the-team-te","errorCode":null,"errorMessage":"User={key.user_id} is not a member of the team={team.team_id}. Check team members via `/team/info`.","messagePattern":"User=(.+?) is not a member of the team=(.+?)\\. Check team members via `/team/info`\\.","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"litellm/proxy/management_endpoints/key_management_endpoints.py","lineNumber":3326,"sourceCode":"\n    # Check if the key's tpm/rpm limit is less than the team's tpm/rpm limit\n    if key.tpm_limit is not None:\n        if team.tpm_limit and key.tpm_limit > team.tpm_limit:\n            raise HTTPException(\n                status_code=403,\n                detail=f\"Key={key.token} has a tpm_limit={key.tpm_limit} which is greater than the team's tpm_limit={team.tpm_limit}.\",\n            )\n        if team.rpm_limit and key.rpm_limit and key.rpm_limit > team.rpm_limit:\n            raise HTTPException(\n                status_code=403,\n                detail=f\"Key={key.token} has a rpm_limit={key.rpm_limit} which is greater than the team's rpm_limit={team.rpm_limit}.\",\n            )\n\n    # Check if the key's user_id is a member of the team\n    member_object: Final = _get_user_in_team(team_table=cast(LiteLLM_TeamTableCachedObj, team), user_id=key.user_id)\n    if key.user_id is not None:\n        if not member_object:\n            raise HTTPException(\n                status_code=403,\n                detail=f\"User={key.user_id} is not a member of the team={team.team_id}. Check team members via `/team/info`.\",\n            )\n\n    # Check if the person initiating the change is a Proxy Admin or Team Admin\n    if (\n        change_initiated_by.user_role == LitellmUserRoles.PROXY_ADMIN.value\n        or _is_user_team_admin(\n            user_api_key_dict=change_initiated_by,\n            team_obj=team,\n        )\n        or TeamMemberPermissionChecks.does_team_member_have_permissions_for_endpoint(\n            team_member_object=member_object,\n            team_table=cast(LiteLLM_TeamTableCachedObj, team),\n            route=KeyManagementRoutes.KEY_UPDATE.value,\n        )\n    ):\n        return","sourceCodeStart":3308,"sourceCodeEnd":3344,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/management_endpoints/key_management_endpoints.py#L3308-L3344","documentation":"For team-scoped keys, LiteLLM validates that the key's user_id refers to an actual member of that team (looked up via _get_user_in_team against the team's membership table/cached object). If the user is not a member, the key create/update is rejected with 403 and a pointer to /team/info. This keeps spend attribution correct: key spend rolls up to a user who must belong to the team.","triggerScenarios":"POST /key/generate or /key/update with team_id X and user_id of a user who has never been added to team X (or was removed from it); creating a key for a user by email when the membership record uses a different user_id.","commonSituations":"New employee provisioned a key before the team-member add completed; user was removed from the team during offboarding but automation still refreshes their key; user_id mismatch between your IdP and LiteLLM's user table.","solutions":["Add the user to the team first: POST /team/member_add with the team_id and the user, then retry the key operation.","Verify membership with GET /team/info and check the members list for the exact user_id.","Or drop user_id from the key request if the key need not be bound to a specific user."],"exampleFix":"# before\nclient.post(\"/key/generate\", json={\"team_id\": tid, \"user_id\": uid, \"models\": [...]})\n\n# after\nclient.post(\"/team/member_add\", json={\"team_id\": tid, \"member\": {\"user_id\": uid, \"role\": \"user\"}})\nclient.post(\"/key/generate\", json={\"team_id\": tid, \"user_id\": uid, \"models\": [...]})","handlingStrategy":"validation","validationCode":"def ensure_user_in_team(client, team_id: str, user_id: str) -> None:\n    info = client.get(\"/team/info\", params={\"team_id\": team_id}).json()\n    members = info.get(\"team_info\", {}).get(\"members\", []) or info.get(\"members\", [])\n    if user_id not in {m.get(\"user_id\") for m in members}:\n        client.post(\"/team/member_add\", json={\"team_id\": team_id, \"member\": {\"user_id\": user_id, \"role\": \"user\"}})","typeGuard":null,"tryCatchPattern":"try:\n    client.post(\"/key/generate\", json={\"team_id\": tid, \"user_id\": uid, ...})\nexcept HTTPError as e:\n    if e.response.status_code == 403 and \"not a member of the team\" in e.response.text:\n        client.post(\"/team/member_add\", json={\"team_id\": tid, \"member\": {\"user_id\": uid, \"role\": \"user\"}})\n        retry_create_key(tid, uid)\n    else:\n        raise","preventionTips":["Model provisioning as: create user -> add to team -> create key, in that order.","Handle team-removal webhooks by also disabling that user's team keys.","Store the exact LiteLLM user_id (not just email) in your directory sync."],"tags":["authorization","team-membership","key-management","litellm-proxy"],"backgroundTag":"permission-denied-403","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}