Skip to content

feat(team): show whether a member follows the team default budget and allow resetting to it - #41906

Merged
ryan-crabbe-berri merged 5 commits into
mainfrom
litellm_team_member_budget_source_reset
Sep 21, 2026
Merged

ryan-crabbe-berri merged 5 commits into
mainfrom
litellm_team_member_budget_source_reset

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

How it solves it:

  • /team/info memberships now carry budget_source: team_default, custom, or none
  • New POST /team/{team_id}/member/{user_id}/reset_budget (proxy, org or team admin) relinks a member to the shared team budget, spend untouched
  • A team default whose budget row was deleted reads as none, not team_default
  • Team members table shows a Team default or Custom badge and a "Use team default" action with a confirm dialog

User Flow

Before: an admin cannot tell which members a team-wide budget change reaches, and cannot undo a member's custom budget

  1. They send POST https://litellm-domain/team/new with {"team_alias": "eng", "team_member_budget": 100} and get a team_id back
  2. They send POST https://litellm-domain/team/member_add with two members and no per-member budget, then POST https://litellm-domain/team/member_update with {"team_id": "...", "user_id": "bob", "max_budget_in_team": 50}
  3. They send GET https://litellm-domain/team/info?team_id=... and every membership shows only a budget_id and a max_budget, nothing says which ones follow the team default
  4. They open https://litellm-domain/ui/?page=teams, click the team, then the Members tab, and the Team Member Budget column shows $100.00 and $50.00 with no indication which is the team default
  5. They want bob back on the team default. POST https://litellm-domain/team/{team_id}/member/bob/reset_budget returns 404 {"detail": "Not Found"}, and the UI has no such action, so their only option is to re-type the team value with /team/member_update, which leaves bob on a private copy that later team changes still miss

After: the same admin sees each member's budget source and can put a member back on the team default in one click

  1. They send POST https://litellm-domain/team/new with {"team_alias": "eng", "team_member_budget": 100} and get a team_id back
  2. They send POST https://litellm-domain/team/member_add with two members and no per-member budget, then POST https://litellm-domain/team/member_update with {"team_id": "...", "user_id": "bob", "max_budget_in_team": 50}
  3. They send GET https://litellm-domain/team/info?team_id=... and each membership now has "budget_source": "team_default" or "budget_source": "custom" (bob)
  4. They open https://litellm-domain/ui/?page=teams, click the team, then the Members tab, and the Team Member Budget column shows a Team default badge on $100.00 and a Custom badge on $50.00, with a "Use team default" link under bob's
  5. They click "Use team default", a dialog shows bob's custom $50.00 and the team default $100.00, they confirm, a success toast appears and bob's row now reads $100.00 with a Team default badge. The same thing over the API is POST https://litellm-domain/team/{team_id}/member/bob/reset_budget, which returns 200 with "budget_source": "team_default" and the previous budget id, and works with a team admin's key as well as the proxy admin's
  6. They later send POST https://litellm-domain/team/update with {"team_id": "...", "team_member_budget": 10} and bob is capped at $10 like everyone else on the default, with his spend so far kept

Relevant issues

Follow-up to #41347 (issue #40783)

Affected release

Linear ticket

Resolves LIT-7716

Pre-Submission checklist

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

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. 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
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to 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

Live proxy on localhost:4000 with Postgres and one real openai/gpt-5.4-mini deployment, master_key: sk-1234. Same setup on both sides: a team with team_member_budget: 100, two members added with no per-member budget, then one of them given max_budget_in_team: 50. $AUTH is -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json". Admin UI screenshots of the Members tab are in the testing recording shared in the review thread rather than embedded here

Before (cda022c)

/team/info does not say which member follows the team default

  1. curl http://localhost:4000/team/new $AUTH -d '{"team_alias":"lit7716-followup","team_member_budget":100}' -> {"team_id":"98c2cbc9-555c-4eb8-b262-bdc23426d3a6","team_member_budget_id":"team-lit7716-followup-budget-fa62d290051b4f9ba644e6b52cd466ee"}
  2. curl http://localhost:4000/team/member_add $AUTH -d '{"team_id":"98c2cbc9-...","member":[{"user_id":"lit7716-inherits-27317","role":"user"},{"user_id":"lit7716-override-27317","role":"user"}]}' -> 200
  3. curl http://localhost:4000/team/member_update $AUTH -d '{"team_id":"98c2cbc9-...","user_id":"lit7716-override-27317","max_budget_in_team":50}' -> {"user_id":"lit7716-override-27317","max_budget_in_team":50}
  4. curl 'http://localhost:4000/team/info?team_id=98c2cbc9-...' $AUTH | jq '.team_memberships[] | {user_id, budget_source, budget_id, max_budget: .litellm_budget_table.max_budget, spend}'
    {"user_id":"default_user_id","budget_source":null,"budget_id":"team-lit7716-followup-budget-fa62d290051b4f9ba644e6b52cd466ee","max_budget":100,"spend":0}
    {"user_id":"lit7716-inherits-27317","budget_source":null,"budget_id":"team-lit7716-followup-budget-fa62d290051b4f9ba644e6b52cd466ee","max_budget":100,"spend":0}
    {"user_id":"lit7716-override-27317","budget_source":null,"budget_id":"48ecd186-927b-422b-8c0d-fcccd41eb024","max_budget":50,"spend":0}
    

No way to put a member back on the team default

  1. curl -X POST http://localhost:4000/team/98c2cbc9-.../member/lit7716-override-27317/reset_budget $AUTH -> {"detail":"Not Found"} HTTP 404

After (8bd9d35)

/team/info says which member follows the team default

  1. curl http://localhost:4000/team/new $AUTH -d '{"team_alias":"lit7716-followup","team_member_budget":100}' | jq '{team_id, team_member_budget_id: .metadata.team_member_budget_id}'
    {"team_id":"1d9cca31-45b1-4296-970b-1c9c86e3c32a","team_member_budget_id":"team-lit7716-followup-budget-bf014edc765b4f4691f7f6b743d76e87"}
    
  2. curl http://localhost:4000/team/member_add $AUTH -d '{"team_id":"1d9cca31-45b1-4296-970b-1c9c86e3c32a","member":[{"user_id":"lit7716-inherits-21408","role":"user"},{"user_id":"lit7716-override-21408","role":"user"}]}' > /dev/null
  3. curl http://localhost:4000/team/member_update $AUTH -d '{"team_id":"1d9cca31-45b1-4296-970b-1c9c86e3c32a","user_id":"lit7716-override-21408","max_budget_in_team":50}' | jq '{user_id, max_budget_in_team}'
    {"user_id":"lit7716-override-21408","max_budget_in_team":50}
    
  4. curl 'http://localhost:4000/team/info?team_id=1d9cca31-45b1-4296-970b-1c9c86e3c32a' $AUTH | jq '.team_memberships[] | {user_id, budget_source, budget_id, max_budget: .litellm_budget_table.max_budget, spend}'
    {"user_id":"default_user_id","budget_source":"team_default","budget_id":"team-lit7716-followup-budget-bf014edc765b4f4691f7f6b743d76e87","max_budget":100,"spend":0}
    {"user_id":"lit7716-inherits-21408","budget_source":"team_default","budget_id":"team-lit7716-followup-budget-bf014edc765b4f4691f7f6b743d76e87","max_budget":100,"spend":0}
    {"user_id":"lit7716-override-21408","budget_source":"custom","budget_id":"214c2d15-d017-416b-a744-41c239328617","max_budget":50,"spend":0}
    

A member can be put back on the team default, by the proxy admin or a team admin, and then follows later /team/update changes

  1. KEY_B=$(curl http://localhost:4000/key/generate $AUTH -d '{"team_id":"1d9cca31-...","user_id":"lit7716-override-21408"}' | jq -r .key), then curl http://localhost:4000/v1/chat/completions -H 'Authorization: Bearer $KEY_B' ... -d '{"model":"gpt-5.4-mini","messages":[{"role":"user","content":"say hi"}],"max_tokens":5}'
    {"id":"chatcmpl-EPdkb9YpVRPFNKEn30KqzA8kUyV37","created":1789779709,"model":"gpt-5.4-mini","object":"chat.completion","choices":[{"finish_reason":"length","index":0,"message":{"content":"Hi!","role":"assistant","provider_specific_fields":{"refusal":null},"annotations":[]},"provider_specific_fields":{}}],"usage":{"completion_tokens":5,"prompt_tokens":8,"total_tokens":13,"completion_tokens_details":
    
  2. curl 'http://localhost:4000/team/info?team_id=1d9cca31-45b1-4296-970b-1c9c86e3c32a' $AUTH | jq '.team_memberships[] | {user_id, budget_source, budget_id, max_budget: .litellm_budget_table.max_budget, spend}'
    {"user_id":"default_user_id","budget_source":"team_default","budget_id":"team-lit7716-followup-budget-bf014edc765b4f4691f7f6b743d76e87","max_budget":100,"spend":0}
    {"user_id":"lit7716-inherits-21408","budget_source":"team_default","budget_id":"team-lit7716-followup-budget-bf014edc765b4f4691f7f6b743d76e87","max_budget":100,"spend":0}
    {"user_id":"lit7716-override-21408","budget_source":"custom","budget_id":"214c2d15-d017-416b-a744-41c239328617","max_budget":50,"spend":2.85e-05}
    
  3. curl -X POST http://localhost:4000/team/1d9cca31-45b1-4296-970b-1c9c86e3c32a/member/lit7716-override-21408/reset_budget $AUTH
    {"team_id":"1d9cca31-45b1-4296-970b-1c9c86e3c32a","user_id":"lit7716-override-21408","budget_id":"team-lit7716-followup-budget-bf014edc765b4f4691f7f6b743d76e87","previous_budget_id":"214c2d15-d017-416b-a744-41c239328617","budget_source":"team_default"}
    HTTP 200
    
  4. curl 'http://localhost:4000/team/info?team_id=1d9cca31-45b1-4296-970b-1c9c86e3c32a' $AUTH | jq '.team_memberships[] | {user_id, budget_source, budget_id, max_budget: .litellm_budget_table.max_budget, spend}'
    {"user_id":"default_user_id","budget_source":"team_default","budget_id":"team-lit7716-followup-budget-bf014edc765b4f4691f7f6b743d76e87","max_budget":100,"spend":0}
    {"user_id":"lit7716-inherits-21408","budget_source":"team_default","budget_id":"team-lit7716-followup-budget-bf014edc765b4f4691f7f6b743d76e87","max_budget":100,"spend":0}
    {"user_id":"lit7716-override-21408","budget_source":"team_default","budget_id":"team-lit7716-followup-budget-bf014edc765b4f4691f7f6b743d76e87","max_budget":100,"spend":2.85e-05}
    
  5. curl http://localhost:4000/team/member_add $AUTH -d '{"team_id":"1d9cca31-45b1-4296-970b-1c9c86e3c32a","member":[{"user_id":"lit7716-teamadmin-21408","role":"admin"}]}' > /dev/null
  6. curl http://localhost:4000/team/member_update $AUTH -d '{"team_id":"1d9cca31-45b1-4296-970b-1c9c86e3c32a","user_id":"lit7716-inherits-21408","max_budget_in_team":25}' > /dev/null
  7. KEY_ADMIN=$(curl http://localhost:4000/key/generate $AUTH -d '{"team_id":"1d9cca31-...","user_id":"lit7716-teamadmin-21408"}' | jq -r .key), then curl -X POST http://localhost:4000/team/1d9cca31-45b1-4296-970b-1c9c86e3c32a/member/lit7716-inherits-21408/reset_budget -H 'Authorization: Bearer $KEY_ADMIN'
    {"team_id":"1d9cca31-45b1-4296-970b-1c9c86e3c32a","user_id":"lit7716-inherits-21408","budget_id":"team-lit7716-followup-budget-bf014edc765b4f4691f7f6b743d76e87","previous_budget_id":"50c5b11e-1a8e-4703-88b3-d5d3c92031dc","budget_source":"team_default"}
    HTTP 200
    
  8. curl http://localhost:4000/team/update $AUTH -d '{"team_id":"1d9cca31-45b1-4296-970b-1c9c86e3c32a","team_member_budget":0.000001}' > /dev/null
  9. curl 'http://localhost:4000/team/info?team_id=1d9cca31-45b1-4296-970b-1c9c86e3c32a' $AUTH | jq '.team_memberships[] | {user_id, budget_source, budget_id, max_budget: .litellm_budget_table.max_budget, spend}'
    {"user_id":"default_user_id","budget_source":"team_default","budget_id":"team-lit7716-followup-budget-bf014edc765b4f4691f7f6b743d76e87","max_budget":1e-06,"spend":0}
    {"user_id":"lit7716-override-21408","budget_source":"team_default","budget_id":"team-lit7716-followup-budget-bf014edc765b4f4691f7f6b743d76e87","max_budget":1e-06,"spend":2.85e-05}
    {"user_id":"lit7716-teamadmin-21408","budget_source":"team_default","budget_id":"team-lit7716-followup-budget-bf014edc765b4f4691f7f6b743d76e87","max_budget":1e-06,"spend":0}
    {"user_id":"lit7716-inherits-21408","budget_source":"team_default","budget_id":"team-lit7716-followup-budget-bf014edc765b4f4691f7f6b743d76e87","max_budget":1e-06,"spend":0}
    
  10. curl http://localhost:4000/v1/chat/completions -H 'Authorization: Bearer $KEY_B' ... (same body)
{"error":{"message":"Budget has been exceeded! TeamMember=lit7716-override-21408:1d9cca31-45b1-4296-970b-1c9c86e3c32a Current cost: 2.85e-05, Max budget: 1e-06","type":"budget_exceeded","param":null,"code":"429"}}
HTTP 429

Type

🆕 New Feature

Caveats (if any)

Medium

Low

  • budget_source is derived from ids at read time, no schema change
  • The reset action lives in the budget column, not in the shared MemberTable action menu

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Note

Medium Risk
Changes team membership budget links and admission-related cache invalidation; mistakes could mis-enforce caps, though scope is limited to team management APIs and mirrors existing reset-spend patterns.

Overview
Adds visibility into whether each team member uses the shared team member budget or a custom budget, and a way to revert customized members back to the team default without touching spend.

API: GET /team/info memberships now include budget_source (team_default, custom, or none), derived from the member’s budget link vs the team’s team_member_budget_id (missing or deleted default rows count as none). New POST /team/{team_id}/member/{user_id}/reset_budget (same admin access as reset spend) reconnects the member to the team default budget or disconnects their budget when no usable default exists; membership spend cache is invalidated after the update.

Dashboard: The Members tab shows Team default / Custom badges, a Use team default confirm flow for customized members (editors only), and refreshes team data after a successful reset.

Tests cover authz, edge cases (no default, deleted default), unit behavior, and UI flows; OpenAPI/schema and endpoint audit allowlist are updated.

Reviewed by Cursor Bugbot for commit 8bd9d35. Bugbot is set up for automated code reviews on this repo. Configure here.

Link to Devin session: https://app.devin.ai/sessions/5d1eee9e522749968ae46d78e845be94
Open in Devin Desktop: https://app.devin.ai/desktop/session/5d1eee9e522749968ae46d78e845be94?variant=devin

ryan-crabbe-berri and others added 2 commits September 19, 2026 00:31
… allow resetting to it

Adds budget_source (team_default, custom, none) to each membership in /team/info and a
POST /team/{team_id}/member/{user_id}/reset_budget route that relinks a member to the team's
shared team_member_budget row without touching their spend. The Admin UI team members table
shows a Team default or Custom badge next to each member's budget and offers a
Use team default action on customized members

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
devin-ai-integration Bot requested a review from a team September 19, 2026 00:38
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@codspeed

codspeed Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_team_member_budget_source_reset (8bd9d35) with main (6d8a960)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (f71129f) during the generation of this report, so 6d8a960 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@codecov

codecov Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.87234% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...tellm/proxy/management_endpoints/team_endpoints.py 97.14% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge; no outstanding correctness, security, or repository-rule violation remains.

Summary

Adds visibility and management for team-member budget inheritance.

  • Derives team_default, custom, or none budget sources in /team/info.
  • Adds an authorized endpoint that reconnects a member to the team’s shared default budget while preserving spend.
  • Adds dashboard badges, a confirmation flow, and tests covering authorization, missing defaults, deleted budget rows, and UI behavior.

Reviews (4) · Last reviewed commit: "test(team): drop docstrings that restate..."

Comment thread litellm/proxy/management_endpoints/team_endpoints.py Outdated
Derive budget_source from the budget row /team/info actually loaded, so a
metadata id whose row was removed via /budget/delete reads as none instead
of team_default. Share the /team/info test scaffolding so the added patch
calls stay within the TQ008 budget, and allowlist the imperative
reset_budget route in the provider endpoint audit

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai

ryan-crabbe-berri and others added 2 commits September 19, 2026 00:58
…ver it in the behavior suite

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…assertions

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai

Comment thread litellm/proxy/management_endpoints/team_endpoints.py
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

@cursor cursor Bot left a comment

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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 8bd9d35. Configure here.

@ryan-crabbe-berri
ryan-crabbe-berri merged commit 5573265 into main Sep 21, 2026
91 of 92 checks passed
@ryan-crabbe-berri
ryan-crabbe-berri deleted the litellm_team_member_budget_source_reset branch September 21, 2026 17:34
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