BerriAI/litellm · error · HTTPException

f"User not found: {user_id}"

Error message

f"User not found: {user_id}"

What it means

GET /v2/user/info runs _check_user_info_v2_access (allows self-lookup, proxy-admin roles, and team-admin over the target) which loads the target user row and returns it when allowed, None otherwise. A None result - either the user does not exist OR the caller is not permitted - produces 404 'User not found: {user_id}'. Returning 404 for both cases avoids leaking which user ids exist.

Source

Thrown at litellm/proxy/management_endpoints/internal_user_endpoints.py:1042

        if user_id is None:
            user_id = user_api_key_dict.user_id

        if user_id is None:
            raise HTTPException(
                status_code=400,
                detail="user_id is required. Either pass it as a query parameter or authenticate with a user-bound key.",
            )

        # Check access — returns the user row if allowed, None otherwise.
        # This avoids a redundant DB fetch since the access check already
        # loads the target user for team-admin verification.
        user_row: Final = await _check_user_info_v2_access(
            user_api_key_dict=user_api_key_dict,
            target_user_id=user_id,
        )

        if user_row is None:
            raise HTTPException(
                status_code=404,
                detail=f"User not found: {user_id}",
            )

        user_data: Final = user_row.model_dump()

        return UserInfoV2Response(
            user_id=user_data.get("user_id", user_id),
            user_email=user_data.get("user_email"),
            user_alias=user_data.get("user_alias"),
            user_role=user_data.get("user_role"),
            spend=user_data.get("spend", 0.0),
            max_budget=user_data.get("max_budget"),
            models=user_data.get("models") or [],
            budget_duration=user_data.get("budget_duration"),
            budget_reset_at=user_data.get("budget_reset_at"),
            metadata=_redact_scim_enterprise_metadata(user_data.get("metadata")),
            created_at=user_data.get("created_at"),

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Verify the id exists via GET /user/list (with an admin key) and fix typos
  2. Use a proxy-admin key, or the target user's own key for self-lookup
  3. If team-based access is expected, confirm the caller is actually an admin of a team that contains the target user

Example fix

# before: internal-user key asking for another user -> 404
GET /v2/user/info?user_id=other-user -H 'Authorization: Bearer sk-internal'

# after: self lookup, or admin key
GET /v2/user/info -H 'Authorization: Bearer sk-internal'
GET /v2/user/info?user_id=other-user -H 'Authorization: Bearer sk-admin'  # 200
Defensive patterns

Strategy: validation

Validate before calling

import requests

def target_readable(base_url: str, admin_headers: dict, target_user_id: str) -> bool:
    r = requests.get(f"{base_url}/user/list", headers=admin_headers, timeout=10)
    r.raise_for_status()
    return any(u.get("user_id") == target_user_id for u in r.json().get("data", []))

Type guard

def will_v2_lookup_succeed(key_role: str, key_user_id: str | None, target: str | None) -> bool:
    # 404 fires when target missing OR not self/admin; caller can only pre-check self/admin
    return key_role in ("proxy_admin", "proxy_admin_viewer") or target == key_user_id

Try / catch

except requests.HTTPError as e:
    if e.response is not None and e.response.status_code == 404:
        # means missing OR forbidden - do not leak existence assumptions; surface as 'unavailable'
        return None
    raise

Prevention

When it happens

Trigger: GET /v2/user/info?user_id=X where X was deleted or never existed; or X exists but the caller is neither X itself, a proxy admin/viewer, nor an admin of a team X belongs to.

Common situations: Non-admin dashboards fetching arbitrary users; stale ids after user deletion; access recently lost via team membership changes; assuming team admins can read any team member here without the team link existing.

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


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/d6582660009a37b9. Report an issue: GitHub.