BerriAI/litellm · error · HTTPException
f"User {user_id} not found"
Error message
f"User {user_id} not found" What it means
After the ownership check passes, GET /user/info fetches the row with prisma_client.get_data(user_id=...). If no row matches the exact id string, LiteLLM returns 404 'User {user_id} not found'. The lookup is exact-match, so encoding differences ('+' decoded to a space), typos, or a user_email passed where a user_id is expected all miss.
Source
Thrown at litellm/proxy/management_endpoints/internal_user_endpoints.py:888
user_id = _normalize_user_info_user_id(request=request, user_id=user_id)
_enforce_user_info_access(user_id=user_id, user_api_key_dict=user_api_key_dict)
if prisma_client is None:
raise Exception(
"Database not connected. Connect a database to your proxy - https://docs.litellm.ai/docs/simple_proxy#managing-auth---virtual-keys"
)
if user_id is None and _user_has_admin_view(user_api_key_dict):
return await _get_user_info_for_proxy_admin(user_api_key_dict=user_api_key_dict)
elif user_id is None:
user_id = user_api_key_dict.user_id
## GET USER ROW ##
user_info = None
if user_id is not None:
user_info = await prisma_client.get_data(user_id=user_id)
if user_info is None:
raise HTTPException(
status_code=404,
detail=f"User {user_id} not found",
)
team_list, teams_1 = await _get_user_info_teams(
prisma_client=prisma_client,
user_id=user_id,
user_info=user_info,
user_api_key_dict=user_api_key_dict,
)
## GET ALL KEYS ##
keys: Final = await prisma_client.get_data(
user_id=user_id,
table_name="key",
query_type="find_all",
)
View on GitHub (pinned to 77b7c6c40c)
Solutions
- List users with GET /user/list and copy the exact user_id
- URL-encode the id (%2B for '+') so it matches the stored value
- Create the user first via POST /user/new (or SSO login) if it must exist
Example fix
# before GET /user/info?user_id=myuser # 404 User myuser not found # after GET /user/list # find exact id, e.g. 'my-user' GET /user/info?user_id=my-user # 200
Defensive patterns
Strategy: validation
Validate before calling
import requests
def user_exists(base_url: str, headers: dict, user_id: str) -> bool:
r = requests.get(f"{base_url}/user/list", headers=headers, timeout=10)
r.raise_for_status()
return any(u.get("user_id") == user_id for u in r.json().get("data", [])) Try / catch
except requests.HTTPError as e:
if e.response is not None and e.response.status_code == 404 and "not found" in e.response.text.lower():
return None # missing resource: skip or re-create, do not blind-retry
raise Prevention
- Persist the exact user_id returned by /user/new and reuse it verbatim
- Encode ids containing '+' or '@' with quote(id, safe='')
- When deleting users, also purge downstream references so later lookups never 404
When it happens
Trigger: GET /user/info?user_id=<deleted or never-created id>; passing an email in a format that differs from the stored user_id; '+' in the id decoded to a space so the exact-match fetch returns None.
Common situations: Referencing users removed by cleanup jobs; ids copy-pasted with whitespace or encoding damage; querying a user whose /user/new call failed earlier in the flow.
Understand the failure class
Background: "User not found", "Invalid user", and "does not exist": what missing-user lookup errors mean across Rocket.Chat, LiteLLM, Phabricator, rustfs, and pnpm — this error's family across 10 libraries.
Related errors
- User not found. Only PROXY_ADMIN can create users via /user/
- LiteLLM Managed File object with id={file_id} not found
- 404
- Organization doesn't exist in db. Organization={org_id}. Cre
- User={valid_token.user_id} has been deactivated via SCIM. Ke
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/42e5b27ba227ad4d.
Report an issue: GitHub.