Repository navigation
feat(team): report per-user spend within a team for JWT traffic - #39771
Conversation
Add GET /team/spend/by_user, which groups raw spend logs by (team_id, user) so JWT/SSO requests with no virtual key are attributed to the user inside each selected team. Team admins see every member, plain members see only their own row. The Team Usage page gets a Spend Per User Within Team card with CSV export backed by the same endpoint. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
|
|
@greptileai please review, this adds GET /team/spend/by_user plus the Team Usage per-user card and CSV export |
Greptile SummaryThis PR adds team-scoped, per-user spend reporting for JWT/SSO traffic and exposes it through the management API and Team Usage dashboard.
Confidence Score: 5/5The PR appears safe to merge, with no outstanding correctness, security, or repository-rule issue identified. No new actionable failure remains after the latest changes. The raw-query and timezone threads were manually resolved after the author supplied implementation context. The source-comment thread was also manually resolved after devin-ai-integration[bot] removed the helper, model, and test docstrings, leaving only the endpoint’s OpenAPI-facing description.
|
| Filename | Overview |
|---|---|
| litellm/proxy/management_endpoints/team_endpoints.py | Adds the scoped per-user team-spend endpoint and removes the explanatory docstrings identified previously. |
| litellm/types/proxy/management_endpoints/team_endpoints.py | Defines the response models for per-user team spend while removing unnecessary model docstrings. |
| ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EntityUsage/EntityUsage.tsx | Wires the per-user spend card into Team Usage with the selected or visible team IDs. |
| ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EntityUsage/TeamUserSpendCard.tsx | Displays authorized team-member spend results and provides CSV download. |
| ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EntityUsage/teamUserSpend.ts | Implements labels, stable row IDs, sorting, formula-safe CSV generation, and download handling. |
| ui/litellm-dashboard/src/components/networking.tsx | Adds the typed client call using the same date boundaries as the existing team activity request. |
| tests/proxy_behavior/management/test_team_spend_by_user.py | Covers the endpoint authorization matrix against the proxy behavior harness. |
| terraform/provider/tools/endpointaudit/coverage_allowlist.txt | Classifies the new read-only reporting endpoint alongside comparable analytics APIs that are not Terraform-managed. |
Reviews (2): Last reviewed commit: "refactor(team): drop explanatory docstri..." | Re-trigger Greptile
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…owlist and EntityUsage unit test Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…and regen schema.d.ts Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
@greptileai please re-review at 5f4496c: docstrings dropped, timezone and raw-query threads answered inline with code references |
dd01abc
into
litellm_internal_staging
TLDR
Problem this solves:
How it solves it:
User Flow
Before: a team admin whose members authenticate with JWTs cannot see how much each member spent inside that team
After: the same traffic is attributed per user inside each team, in the API, the UI, and a CSV
Relevant issues
Linear ticket
Resolves LIT-6948
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py, route access intests/test_litellm/proxy/auth/test_route_checks.py, the actor x team authz matrix intests/proxy_behavior/management/test_team_spend_by_user.py(27 cases against the ASGI proxy with a real Postgres), and dashboard unit tests for the CSV/label helpers plus the EntityUsage wiring. Each was mutation-checked (dropping the route from the access lists fails 24/27 behavior cases, dropping thelitellm-dashboardfilter fails the EntityUsage test)uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Setup shared by both arms. Postgres + proxy on localhost:4000 started from this checkout (
PYTHONPATH=$PWD), config enabling JWT auth against a local JWKS withteam_id_jwt_field: billable_id,user_id_jwt_field: email,user_id_upsert: true, plus an Anthropichaikudeployment. Teamsteam-alpha(alias Team Alpha, alice admin, bob member) andteam-beta(alias Team Beta, alice member). Three real chat completions were sent with JWTs and no virtual key: alice in team-alpha twice, bob in team-alpha once, alice in team-beta once.$MASTERis the master key,$JWT_*are the minted JWTsBefore (2e73400)
Team view has no per-user split
curl -s "localhost:4000/team/daily/activity/aggregated?team_ids=team-alpha&start_date=2026-09-04&end_date=2026-09-04" -H "Authorization: Bearer $MASTER" | jq ".results[0].breakdown | {api_keys, entities}"api_keys: {}(JWT traffic has no key) and a singleteam-alphaentity withapi_requests: 3,spend: 0.000102, no user dimensionUser view merges teams
curl -s "localhost:4000/user/daily/activity?user_id=alice@example.com&start_date=2026-09-04&end_date=2026-09-04" -H "Authorization: Bearer $MASTER" | jq ".metadata | {total_spend, total_api_requests}"{"total_spend": 0.000102, "total_api_requests": 3}, which is alice's Team Alpha and Team Beta traffic added togetherPer-user-within-team endpoint
curl -s -w "\nHTTP %{http_code}\n" "localhost:4000/team/spend/by_user?team_ids=team-alpha&start_date=2026-09-04&end_date=2026-09-04" -H "Authorization: Bearer $MASTER"{"detail":"Not Found"}HTTP 404Admin UI
After (5f4496c)
Team view has no per-user split
team/daily/activity/aggregatedcall as Beforeteam-alphaentity, 3 requests (this endpoint is out of scope)User view merges teams
user/daily/activitycall as BeforePer-user-within-team endpoint
curl -s "localhost:4000/team/spend/by_user?team_ids=team-alpha,team-beta&start_date=2026-09-04&end_date=2026-09-04" -H "Authorization: Bearer $MASTER" | jq -c ".results[]|{team_id,team_alias,user_id,spend,api_requests,successful_requests,failed_requests,total_tokens}"curl -s -w "\nHTTP %{http_code}\n" "localhost:4000/team/spend/by_user?team_ids=team-alpha&start_date=2026-09-04&end_date=2026-09-04" -H "Authorization: Bearer $JWT_ALICE_ALPHA" | jq -c ".results[]|{user_id,spend,api_requests}"{"user_id":"alice@example.com","spend":6.8e-05,"api_requests":2}and{"user_id":"bob@example.com","spend":3.4e-05,"api_requests":1},HTTP 200$JWT_BOB_ALPHA{"user_id":"bob@example.com","spend":3.4e-05,"api_requests":1},HTTP 200team_ids=team-betawith$JWT_ALICE_BETA{"user_id":"alice@example.com","spend":3.4e-05,"api_requests":1},HTTP 200team_ids=team-betawith$JWT_BOB_ALPHA{"detail":{"error":"User does not belong to Team= team-beta. Call/user/infoto see user's teams"}}HTTP 404curl -s -w "\nHTTP %{http_code}\n" "localhost:4000/team/spend/by_user?start_date=2026-09-04&end_date=2026-09-04" -H "Authorization: Bearer $MASTER"{"detail":{"error":"Please provide team_ids"}}HTTP 400team_ids=team-alpha&start_date=2026-08-01&end_date=2026-08-31{"start_date":"2026-08-01","end_date":"2026-08-31","results":[]}HTTP 200Admin UI
npm run devinui/litellm-dashboard, open http://localhost:3000/usage, choose Team Usage in the Usage View dropdown, scroll below Spend Per Teamteam_ids=team-beta(proxy log showsGET /team/spend/by_user?...&team_ids=team-beta) and shows only Team Beta / alice@example.com (1 request), Spend Per Team drops to Team Beta onlyteam_user_spend_2026-08-28_to_2026-09-04.csvdownloaded with headerStart Date,End Date,Team,Team ID,User,User ID,User Email,Spend (USD),Requests,Successful,Failed,Prompt Tokens,Completion Tokens,Total Tokensand the same three rows, e.g.2026-08-28,2026-09-04,Team Alpha,team-alpha,alice@example.com,alice@example.com,alice@example.com,0.000068,2,2,0,18,10,28Type
🆕 New Feature
Caveats (if any)
Medium
Low
(no user)in the UIFinal Attestation
Link to Devin session: https://app.devin.ai/sessions/b39d9e85349e45e795ff3979df4acf2d
Open in Devin Desktop: https://app.devin.ai/desktop/session/b39d9e85349e45e795ff3979df4acf2d?variant=devin
Requested by: @yassin-berriai