Skip to content

[Feature] User Info V2 Endpoint - #23437

Merged
yuneng-jiang merged 5 commits into
litellm_internal_dev_03_12_2026from
litellm_user-info-endpoint-v2-24cc
Mar 13, 2026
Merged

[Feature] User Info V2 Endpoint#23437
yuneng-jiang merged 5 commits into
litellm_internal_dev_03_12_2026from
litellm_user-info-endpoint-v2-24cc

Conversation

@yuneng-jiang

Copy link
Copy Markdown
Contributor

Relevant issues

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

CI (LiteLLM team)

CI status guideline:

  • 50-55 passing tests: main is stable with minor issues.
  • 45-49 passing tests: acceptable but needs attention
  • <= 40 passing tests: unstable; be careful with your merges and assess the risk.
  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Type

🆕 New Feature
🐛 Bug Fix
🧹 Refactoring
📖 Documentation
🚄 Infrastructure
✅ Test

Changes

This PR introduces a new /v2/user/info endpoint and migrates relevant UI flows to use it, addressing the stability and memory issues caused by the existing /user/info "god endpoint".

Key Changes:

  • New GET /v2/user/info Endpoint:
    • Returns only the LiteLLM_UserTable object, excluding keys and team objects, to reduce data transfer and memory footprint.
    • Implements granular Role-Based Access Control (RBAC):
      • Proxy admins can request any user's info.
      • Team admins can request info for users within their team.
      • Internal users can request their own info (by omitting user_id or passing their own).
    • Returns a 404 Not Found status for unauthorized access or non-existent users, instead of 403 Forbidden, to prevent user enumeration.
    • Includes a new UserInfoV2Response Pydantic model, route registration, and route check.
    • Adds user_get_info_v2 method to the Python client.
  • UI Migration:
    • The useCurrentUser hook now utilizes userGetInfoV2 for self-lookup.
    • user_dashboard.tsx is updated to fetch spend data from /v2/user/info; keys are now sourced from the dedicated useKeys hook, removing redundancy.
    • user_info_view.tsx is refactored to fetch core user details from /v2/user/info, with team details fetched separately via teamInfoCall.
  • Testing:
    • Added 9 new Python unit tests for the /v2/user/info endpoint, covering various access scenarios and response types.
    • Added 2 new tests for route checks related to the new endpoint.
    • Updated UI Vitest tests for useCurrentUser, user_dashboard, and user_info_view to reflect the new data fetching patterns.
    • Manual API and UI testing (including a screen recording) confirmed the correct functionality and integration of the new endpoint.

The original /user/info endpoint remains untouched for backward compatibility. Organization admin access was intentionally skipped as per requirements.

Open in Web Open in Cursor 

cursoragent and others added 4 commits March 12, 2026 07:43
- Add UserInfoV2Response type in _types.py (returns only user object, no keys/teams)
- Add /v2/user/info endpoint handler with proper access control:
  - Proxy admins can query any user
  - Team admins can query users in their teams
  - Internal users can query themselves only
  - Returns 404 for unauthorized/not-found (not 403)
- Add /v2/user/info to info_routes in LiteLLMRoutes
- Add route check passthrough in route_checks.py
- Add get_user_v2() method to Python client

Co-authored-by: yuneng-jiang <yuneng-jiang@users.noreply.github.com>
- 9 tests for the endpoint: admin access, self-lookup, unauthorized access,
  default to self, nonexistent user, response shape, team admin access,
  team admin denied, URL encoding
- 2 tests for route checks: route in info_routes, route access control

Co-authored-by: yuneng-jiang <yuneng-jiang@users.noreply.github.com>
…tUser hook

- Add UserInfoV2Response type and userGetInfoV2() function in networking.tsx
- Migrate useCurrentUser hook from userInfoCall to userGetInfoV2
- Update useCurrentUser.test.ts to test new v2 API integration
- The hook no longer needs userRole since the endpoint handles auth itself

Co-authored-by: yuneng-jiang <yuneng-jiang@users.noreply.github.com>
…er/info

- user_dashboard.tsx: Replace userInfoCall with userGetInfoV2 for spend data,
  remove keys/teams logic (keys come from props/useKeys hook, teams from fetchTeams)
- user_info_view.tsx: Replace userInfoCall with userGetInfoV2, flatten data
  structure from nested {user_info: {...}} to flat response, fetch team details
  separately using teamInfoCall, remove keys display (Virtual Keys section)
- Update user_dashboard.test.tsx and user_info_view.test.tsx mocks

Co-authored-by: yuneng-jiang <yuneng-jiang@users.noreply.github.com>
@cursor

cursor Bot commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@vercel

vercel Bot commented Mar 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Error Error Mar 13, 2026 0:13am

Request Review

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@yuneng-jiang yuneng-jiang changed the title User info endpoint v2 [Feature] User Info V2 Endpoint Mar 13, 2026
@yuneng-jiang
yuneng-jiang marked this pull request as ready for review March 13, 2026 00:00
@greptile-apps

greptile-apps Bot commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a new GET /v2/user/info endpoint as a lightweight alternative to the existing /user/info "god endpoint", returning only the LiteLLM_UserTable object (no keys, no full team objects) to reduce memory footprint and improve stability. The UI is migrated to use the new endpoint across useCurrentUser, user_dashboard, and user_info_view.

Key changes:

  • New user_info_v2 endpoint with granular RBAC (proxy admins → any user; team admins → team members; users → self-only) and 404-on-unauthorized to prevent user enumeration
  • UserInfoV2Response Pydantic model added; endpoint registered in info_routes
  • useCurrentUser hook simplified: no longer requires userRole to be enabled, directly returns the flat UserInfoV2Response instead of the nested user_info sub-object
  • user_dashboard.tsx now fetches only spend data via userGetInfoV2; keys are delegated to the useKeys hook
  • user_info_view.tsx fetches team aliases via individual teamInfoCall per team ID (N+1 pattern) — for users belonging to many teams this fires many concurrent requests
  • _check_user_info_v2_access helper returns None when prisma_client is None instead of raising, which the outer endpoint handles, but makes the helper misleading when called in isolation
  • 11 new tests added (9 unit + 2 route checks), all using mocks with no real network calls

Confidence Score: 4/5

  • Safe to merge with minor caveats — RBAC logic is correct and the endpoint is well-tested, but the N+1 team info calls in the UI may cause latency for users in many teams.
  • The backend endpoint is well-designed with proper RBAC, user-enumeration-safe 404 responses, thorough unit tests, and no breaking changes to the existing /user/info endpoint. The UI migration is clean. The main concerns are: (1) the N+1 teamInfoCall pattern in user_info_view.tsx fires one API request per team and can degrade UI performance at scale, and (2) _check_user_info_v2_access silently returns None on a missing Prisma client rather than raising, which is safe in the current call graph but fragile for future callers.
  • ui/litellm-dashboard/src/components/view_users/user_info_view.tsx — N+1 teamInfoCall requests. litellm/proxy/management_endpoints/internal_user_endpoints.pyprisma_client is None silent None return in helper.

Important Files Changed

Filename Overview
litellm/proxy/management_endpoints/internal_user_endpoints.py Adds _check_user_info_v2_access helper and user_info_v2 endpoint. RBAC logic is correct; minor issue: prisma_client is None guard returns None instead of raising, which could produce a misleading 404 if the helper is called without the outer guard.
litellm/proxy/_types.py Adds UserInfoV2Response Pydantic model and registers /v2/user/info in info_routes. Clean, well-structured addition with correct field types and defaults.
litellm/proxy/auth/route_checks.py Adds a pass-through case for /v2/user/info in non_proxy_admin_allowed_routes_check, deferring all access control to the endpoint handler. The comment clearly explains the rationale.
litellm/proxy/client/users.py Adds get_user_v2 method to the Python client. Clean implementation with appropriate error handling for 401 and 404 status codes.
ui/litellm-dashboard/src/components/networking.tsx Adds UserInfoV2Response interface and userGetInfoV2 function. Follows existing patterns in the file for error handling and URL construction.
ui/litellm-dashboard/src/components/view_users/user_info_view.tsx Migrated to userGetInfoV2 with separate teamInfoCall per team ID. The N+1 pattern (one teamInfoCall per team, parallelised) could cause a large number of concurrent requests for users in many teams.
ui/litellm-dashboard/src/components/user_dashboard.tsx Replaces userInfoCall with userGetInfoV2 for spend data; keys now sourced from useKeys hook. The !keys guard and its sessionStorage write were both removed cleanly (no dangling reads remain).
ui/litellm-dashboard/src/app/(dashboard)/hooks/users/useCurrentUser.ts Switched from userInfoCall to userGetInfoV2 for self-lookup. Removes userRole from the enabled condition (now only requires accessToken && userId), which is appropriate for a self-lookup endpoint.
tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py Adds 9 well-structured async tests covering proxy admin, self-lookup, cross-user access denial, team admin access, non-team-member denial, URL encoding, and response shape. All use mocks; no real network calls.
tests/test_litellm/proxy/auth/test_info_routes.py Adds 2 tests verifying /v2/user/info is in info_routes and that route-level access control passes through to the endpoint handler. Clean and correct.

Sequence Diagram

sequenceDiagram
    participant Client
    participant FastAPI
    participant user_info_v2
    participant _check_user_info_v2_access
    participant DB as Prisma DB

    Client->>+FastAPI: GET /v2/user/info?user_id=X
    FastAPI->>+user_info_v2: user_api_key_dict, user_id
    user_info_v2->>+_check_user_info_v2_access: (caller, target_user_id)

    alt Proxy admin / admin viewer
        _check_user_info_v2_access->>DB: find_unique(target_user)
        DB-->>_check_user_info_v2_access: user_row
    else Self-lookup (caller == target)
        _check_user_info_v2_access->>DB: find_unique(caller == target)
        DB-->>_check_user_info_v2_access: user_row
    else Team admin check
        _check_user_info_v2_access->>DB: find_unique(caller_user)
        DB-->>_check_user_info_v2_access: caller_row
        _check_user_info_v2_access->>DB: find_unique(target_user)
        DB-->>_check_user_info_v2_access: target_row
        _check_user_info_v2_access->>DB: find_many(caller's teams)
        DB-->>_check_user_info_v2_access: teams[]
        Note over _check_user_info_v2_access: Check if caller is admin<br/>of any team containing target
    end

    _check_user_info_v2_access-->>-user_info_v2: user_row or None

    alt user_row is None
        user_info_v2-->>Client: 404 Not Found
    else user_row found
        user_info_v2-->>-FastAPI: UserInfoV2Response (flat: no keys, team IDs only)
        FastAPI-->>Client: 200 OK
    end
Loading

Last reviewed commit: cd1b31b

Comment on lines +771 to +774
except Exception:
verbose_proxy_logger.debug(
f"Error checking team admin access for user {user_api_key_dict.user_id}"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent exception swallows real DB errors

The bare except Exception: pass with only a debug-level log means that genuine DB failures (e.g. connection timeouts, query errors) are silently converted into a False return. From the caller's perspective this is indistinguishable from "access denied", so the endpoint will return 404 even when the real cause is a transient infrastructure issue — making it extremely hard to diagnose in production.

Consider logging at warning or error level so that operator-level failures surface in observability tooling:

Suggested change
except Exception:
verbose_proxy_logger.debug(
f"Error checking team admin access for user {user_api_key_dict.user_id}"
)
except Exception as e:
verbose_proxy_logger.warning(
f"Error checking team admin access for user {user_api_key_dict.user_id}: {e}"
)

Comment on lines +762 to +770
# Check if target user is in this team
target_user = await prisma_client.db.litellm_usertable.find_unique(
where={"user_id": target_user_id}
)
if (
target_user is not None
and team.team_id in (target_user.teams or [])
):
return True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

N+1 DB query inside team loop

The litellm_usertable.find_unique call for target_user_id is executed inside the for team in teams loop. If the caller is an admin of N teams, the same database row is fetched N times despite the result being identical on every iteration.

The fix is to move the target_user fetch to just before the loop begins, then reference the already-fetched object inside the loop when checking team.team_id in (target_user.teams or []). This reduces the query count from O(N teams) to a single round-trip.

…_user_info_v2_access

Three bugs fixed in the RBAC helper for /v2/user/info:

1. N+1 query: target user was fetched inside the team loop, causing one
   redundant DB round-trip per team the caller administers. Now hoisted
   before the loop — single fetch regardless of team count.

2. Double fetch: the handler performed a second find_unique on the target
   user after the access check already fetched it. The helper now returns
   the user row directly (Optional[row] instead of bool), so the handler
   reuses it.

3. Silent error swallowing: a bare except Exception caught real DB errors
   (connection failures, timeouts) and returned False, surfacing them as
   mysterious 404s. Removed the try/except so real errors propagate as 500s.

Co-authored-by: yuneng-jiang <yuneng-jiang@users.noreply.github.com>
Comment on lines +80 to +97
const teamPromises = data.teams.map(async (teamId: string) => {
try {
const teamData = await teamInfoCall(accessToken, teamId);
return {
team_id: teamId,
team_alias: teamData?.team_alias || null,
};
} catch {
return { team_id: teamId, team_alias: null };
}
});
const teams = await Promise.all(teamPromises);
setTeamDetails(teams);
} catch {
// Fall back to just team IDs
setTeamDetails(data.teams.map((id: string) => ({ team_id: id, team_alias: null })));
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

N+1 teamInfoCall requests per team

The component fires one teamInfoCall per team ID in parallel. For users that belong to many teams (e.g., 50+), this results in 50+ simultaneous HTTP requests to /team/info, each returning a full team object (including members_with_roles) when only team_alias is needed.

Consider caching team aliases or batching by adding a multi-team-alias endpoint, or at minimum limit the parallel calls:

// Only fetch aliases for the first 20 teams shown, fetch more lazily
const visibleTeamIds = data.teams.slice(0, 20);
const teamPromises = visibleTeamIds.map(async (teamId: string) => {
  try {
    const teamData = await teamInfoCall(accessToken, teamId);
    return { team_id: teamId, team_alias: teamData?.team_alias || null };
  } catch {
    return { team_id: teamId, team_alias: null };
  }
});

At a minimum, the rest of the teams beyond the initial visible 20 could be fetched on demand when the user clicks "show more".

Comment on lines +738 to +741
from litellm.proxy.proxy_server import prisma_client

if prisma_client is None:
return None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prisma_client is None returns None, causing misleading 404

When prisma_client is None, _check_user_info_v2_access returns None. The caller (user_info_v2) treats None as "access denied / user not found" and raises a 404. However, user_info_v2 already guards against this with its own if prisma_client is None: raise HTTPException(500) check before calling this function.

The issue is that _check_user_info_v2_access is a standalone helper function. If it is ever called from a context that does not first check prisma_client, a DB-unavailable condition would silently produce a None return indistinguishable from a legitimate "access denied" result.

Consider raising an exception here to make the failure loud:

if prisma_client is None:
    raise HTTPException(
        status_code=500,
        detail=CommonProxyErrors.db_not_connected_error.value,
    )

@yuneng-jiang
yuneng-jiang merged commit beb521b into litellm_internal_dev_03_12_2026 Mar 13, 2026
29 of 53 checks passed
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…int-v2-24cc

[Feature] User Info V2 Endpoint
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants