diff --git a/litellm/models/team.py b/litellm/models/team.py
index 8edf10703b1..46385daba56 100644
--- a/litellm/models/team.py
+++ b/litellm/models/team.py
@@ -7,6 +7,7 @@ budget-window value types and the team-model alias table). Re-exported from
"""
import json
+import math
from datetime import datetime
from typing import Final, Literal, Optional
@@ -137,6 +138,18 @@ class LiteLLM_TeamTableCachedObj(LiteLLM_TeamTable):
last_refreshed_at: float | None = None
+def team_member_budget_allows_overflow(team: LiteLLM_TeamTable, member_max_budget: float | None) -> bool:
+ return (
+ team.metadata is not None
+ and team.metadata.get("allow_team_member_budget_overflow") is True
+ and team.max_budget is not None
+ and math.isfinite(team.max_budget)
+ and team.max_budget > 0
+ and member_max_budget is not None
+ and member_max_budget > 0
+ )
+
+
class LiteLLM_DeletedTeamTable(LiteLLM_TeamTable):
"""Audit record for deleted teams; mirrors the team plus deletion metadata."""
diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py
index b6de36f8423..a70ff849997 100644
--- a/litellm/proxy/_types.py
+++ b/litellm/proxy/_types.py
@@ -2095,6 +2095,11 @@ RouterSettingsDict = Annotated[
class NewTeamRequest(TeamBase):
+ allow_team_member_budget_overflow: bool | None = Field(
+ default=None,
+ strict=True,
+ description="Allow positive member budgets to use remaining total team budget. Defaults to false; requires a finite positive team max_budget.",
+ )
router_settings: RouterSettingsDict | None = None
model_aliases: dict | None = None
model_max_budget: GenericBudgetConfigType | None = Field(
@@ -2167,6 +2172,11 @@ class UpdateTeamRequest(LiteLLMPydanticObjectBase):
policies: Optional[List[str]] = None
"""
+ allow_team_member_budget_overflow: bool | None = Field(
+ default=None,
+ strict=True,
+ description="Allow positive member budgets to use remaining total team budget. Requires a finite positive team max_budget.",
+ )
team_id: str # required
team_alias: str | None = None
organization_id: str | None = None
diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py
index 67950e603c0..96f597371e3 100644
--- a/litellm/proxy/auth/auth_checks.py
+++ b/litellm/proxy/auth/auth_checks.py
@@ -5650,7 +5650,9 @@ async def _check_team_member_budget(
member_budget_row.active_temp_budget_increase(now=now) if member_budget_row is not None else 0.0
)
- if team_member_budget is not None:
+ from litellm.models.team import team_member_budget_allows_overflow
+
+ if team_member_budget is not None and not team_member_budget_allows_overflow(team_object, team_member_budget):
team_member_spend = (loaded_membership.spend if loaded_membership is not None else 0.0) or 0.0
# Read from cross-pod counter (Redis-first) if available
diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py
index 8a6cd1218ee..47a72dd01b7 100644
--- a/litellm/proxy/management_endpoints/team_endpoints.py
+++ b/litellm/proxy/management_endpoints/team_endpoints.py
@@ -557,6 +557,44 @@ def _caller_edit_access(role: TeamAccessRole | None, general_settings: Mapping[s
assert_never(role)
+def _team_member_budget_overflow_metadata(
+ data: NewTeamRequest | UpdateTeamRequest,
+ existing_team: LiteLLM_TeamTable | None = None,
+) -> Mapping[str, JsonValue] | None:
+ supplied_metadata: Final = (
+ data.metadata if "metadata" in data.model_fields_set or existing_team is None else existing_team.metadata
+ )
+ metadata: Final = (
+ TypeAdapter(dict[str, JsonValue]).validate_python(supplied_metadata) if supplied_metadata is not None else None
+ )
+ resulting_metadata: Final = (
+ TypeAdapter(dict[str, JsonValue]).validate_python(
+ MappingProxyType(
+ {
+ **(metadata if metadata is not None else MappingProxyType({})),
+ "allow_team_member_budget_overflow": data.allow_team_member_budget_overflow,
+ }
+ )
+ )
+ if data.allow_team_member_budget_overflow is not None
+ else metadata
+ )
+ enabled: Final = (
+ resulting_metadata.get("allow_team_member_budget_overflow", False) if resulting_metadata is not None else False
+ )
+ if enabled is not None and not isinstance(enabled, bool):
+ raise HTTPException(status_code=400, detail="allow_team_member_budget_overflow must be a boolean")
+ max_budget: Final = (
+ data.max_budget if "max_budget" in data.model_fields_set or existing_team is None else existing_team.max_budget
+ )
+ if enabled is True and (max_budget is None or not math.isfinite(max_budget) or max_budget <= 0):
+ raise HTTPException(
+ status_code=400,
+ detail="allow_team_member_budget_overflow requires a finite positive team max_budget",
+ )
+ return resulting_metadata
+
+
class TeamMemberBudgetHandler:
"""Helper class to handle team member budget, RPM, and TPM limit operations"""
@@ -1662,9 +1700,10 @@ async def new_team(
if isinstance(data.metadata, dict):
TeamMemberBudgetHandler.strip_system_managed_metadata_keys(data.metadata)
+ overflow_metadata: Final = _team_member_budget_overflow_metadata(data)
await validate_team_metadata_if_configured(
operation="create",
- metadata=data.metadata,
+ metadata=overflow_metadata,
existing_metadata=None,
team_id=data.team_id,
team_alias=data.team_alias,
@@ -1690,7 +1729,9 @@ async def new_team(
_model_id = model_dict.id
- data_json = data.json()
+ data_json = data.model_copy(update=MappingProxyType({"metadata": overflow_metadata})).json(
+ exclude=frozenset(("allow_team_member_budget_overflow",))
+ )
## Handle Object Permission - MCP, Vector Stores etc.
await enforce_all_proxy_mcp_servers_grant_is_admin_only(
@@ -2446,7 +2487,10 @@ async def update_team(
existing_model_max_budget=existing_team_row.model_max_budget,
)
- updated_kv = data.json(exclude_unset=True)
+ overflow_metadata: Final = _team_member_budget_overflow_metadata(data, existing_team)
+ updated_kv = data.json(exclude_unset=True, exclude=frozenset(("allow_team_member_budget_overflow",)))
+ if data.allow_team_member_budget_overflow is not None:
+ updated_kv["metadata"] = overflow_metadata
if "model_max_budget" in updated_kv and updated_kv["model_max_budget"] is None:
updated_kv["model_max_budget"] = {}
@@ -2498,9 +2542,9 @@ async def update_team(
if isinstance(existing_team_row.metadata, dict):
if "metadata" not in updated_kv and (_team_member_fields_in_request or _writes_metadata_backed_field):
updated_kv["metadata"] = copy.deepcopy(existing_team_row.metadata)
- elif isinstance(updated_kv.get("metadata"), dict):
+ elif isinstance(update_metadata := updated_kv.get("metadata"), dict):
updated_kv["metadata"] = {
- **updated_kv["metadata"],
+ **update_metadata,
**{
key: existing_team_row.metadata[key]
for key in TeamMemberBudgetHandler.SYSTEM_MANAGED_METADATA_KEYS
diff --git a/litellm/proxy/spend_tracking/budget_reservation.py b/litellm/proxy/spend_tracking/budget_reservation.py
index e28fa2c06a4..155946b50c2 100644
--- a/litellm/proxy/spend_tracking/budget_reservation.py
+++ b/litellm/proxy/spend_tracking/budget_reservation.py
@@ -778,7 +778,13 @@ async def _get_team_member_budget_counter(
member_budget_row.active_temp_budget_increase(now=now) if member_budget_row is not None else 0.0
)
- if team_member_budget is None or team_member_budget <= 0:
+ from litellm.models.team import team_member_budget_allows_overflow
+
+ if (
+ team_member_budget is None
+ or team_member_budget <= 0
+ or team_member_budget_allows_overflow(team_object, team_member_budget)
+ ):
return None
team_member_spend = cast(LiteLLM_TeamMembership, team_membership).spend if team_membership is not None else 0.0
diff --git a/tests/test_litellm/proxy/auth/test_auth_checks.py b/tests/test_litellm/proxy/auth/test_auth_checks.py
index 30f5abdbb98..ce00ecce9d0 100644
--- a/tests/test_litellm/proxy/auth/test_auth_checks.py
+++ b/tests/test_litellm/proxy/auth/test_auth_checks.py
@@ -9456,3 +9456,95 @@ def test_can_object_call_model_allows_listed_model_for_key():
)
assert result is True
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize(
+ "enabled,team_cap,member_cap,spend,denied",
+ [
+ (None, 10.0, 1.0, 1.0, True),
+ (False, 10.0, 1.0, 2.0, True),
+ (True, 10.0, 1.0, 1.0, False),
+ (True, 10.0, 1.0, 2.0, False),
+ (True, 10.0, 0.0, 0.0, True),
+ (True, 10.0, None, 2.0, False),
+ (True, None, 1.0, 2.0, True),
+ (True, 0.0, 1.0, 2.0, True),
+ (True, float("inf"), 1.0, 2.0, True),
+ (True, float("nan"), 1.0, 2.0, True),
+ ("true", 10.0, 1.0, 2.0, True),
+ ],
+)
+async def test_team_member_overflow_preserves_explicit_zero_and_requires_bounded_team(
+ monkeypatch: pytest.MonkeyPatch,
+ enabled: bool | str | None,
+ team_cap: float | None,
+ member_cap: float | None,
+ spend: float,
+ denied: bool,
+) -> None:
+ from litellm.caching import DualCache
+ from litellm.models.team_membership import LiteLLM_TeamMembership
+ from litellm.proxy import proxy_server
+ from litellm.proxy.utils import ProxyLogging
+
+ cache: Final = UserApiKeyCache()
+ monkeypatch.setattr(proxy_server, "spend_counter_cache", DualCache())
+ membership: Final = LiteLLM_TeamMembership(
+ user_id="overflow-member", team_id="overflow-team", spend=spend,
+ litellm_budget_table=LiteLLM_BudgetTable(max_budget=member_cap),
+ )
+ check: Final = _check_team_member_budget(
+ team_object=LiteLLM_TeamTable(
+ team_id="overflow-team", max_budget=team_cap,
+ metadata={"allow_team_member_budget_overflow": enabled},
+ ),
+ user_object=LiteLLM_UserTable(user_id="overflow-member"),
+ valid_token=UserAPIKeyAuth(user_id="overflow-member", team_id="overflow-team"),
+ prisma_client=None, user_api_key_cache=cache,
+ proxy_logging_obj=ProxyLogging(user_api_key_cache=cache),
+ team_membership=membership, team_membership_loaded=True,
+ )
+ if denied:
+ with pytest.raises(litellm.BudgetExceededError) as error:
+ await check
+ assert error.value.max_budget == member_cap
+ assert error.value.current_cost == spend
+ else:
+ assert await check is None
+ assert membership.spend == spend
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("enabled", [False, True])
+async def test_team_member_overflow_uses_live_default_after_temporary_grant_expiry(enabled: bool) -> None:
+ from litellm.models.team_membership import LiteLLM_TeamMembership
+ from litellm.proxy.utils import ProxyLogging
+
+ cache: Final = UserApiKeyCache()
+ await cache.async_set_cache(
+ key="team_member_default_budget:overflow-default",
+ value=LiteLLM_BudgetTable(budget_id="overflow-default", max_budget=1.0),
+ )
+ check: Final = _check_team_member_budget(
+ team_object=LiteLLM_TeamTable(
+ team_id="overflow-default-team", max_budget=10.0,
+ metadata={"team_member_budget_id": "overflow-default", "allow_team_member_budget_overflow": enabled},
+ ),
+ user_object=None, valid_token=UserAPIKeyAuth(user_id="overflow-default-member"),
+ prisma_client=MagicMock(), user_api_key_cache=cache,
+ proxy_logging_obj=ProxyLogging(user_api_key_cache=cache),
+ team_membership=LiteLLM_TeamMembership(
+ user_id="overflow-default-member", team_id="overflow-default-team", spend=2.0,
+ litellm_budget_table=LiteLLM_BudgetTable(
+ temp_budget_increase=5.0, temp_budget_expiry=datetime.now(timezone.utc) - timedelta(days=1),
+ ),
+ ),
+ team_membership_loaded=True,
+ )
+ if enabled:
+ assert await check is None
+ else:
+ with pytest.raises(litellm.BudgetExceededError) as error:
+ await check
+ assert error.value.max_budget == 1.0
diff --git a/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py
index 6d902cb7fec..e28f3e0b6fc 100644
--- a/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py
+++ b/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py
@@ -9,7 +9,7 @@ from unittest.mock import AsyncMock, MagicMock, PropertyMock, call, patch
import httpx
import pytest
-from fastapi import HTTPException
+from fastapi import HTTPException, Request
from fastapi.testclient import TestClient
from pydantic import ValidationError
@@ -17228,3 +17228,103 @@ def test_team_export_csv_escapes_formula_aliases_and_keeps_dash_placeholder():
assert record["Key Alias"] == "'@cmd"
assert record["User ID"] == "-"
assert record["User Email"] == "-"
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("kind", ["post", "patch"])
+@pytest.mark.parametrize("cap", [None, 0.0, -1.0, float("inf"), float("nan")])
+async def test_team_overflow_rejects_removing_or_invalidating_enabled_hard_cap(kind: str, cap: float | None) -> None:
+ with pytest.raises(ProxyException) as error:
+ await _drive_team_write(
+ kind, existing_metadata={"allow_team_member_budget_overflow": True},
+ existing_kwargs={"max_budget": 10.0}, payload={"max_budget": cap},
+ )
+ assert str(error.value.code) == "400"
+ assert "max_budget" in str(error.value.message)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("kind", ["post", "patch"])
+@pytest.mark.parametrize("enabled", [False, True])
+async def test_team_overflow_update_preserves_metadata_and_toggles_policy(kind: str, enabled: bool) -> None:
+ recorded: Final = []
+ stored: Final = {"cost_center": "budget-test", "team_member_budget_id": "member-budget"}
+ with _configured_team_metadata_validator(_recording_validator(recorded)):
+ _, write = await _drive_team_write(
+ kind, existing_metadata=stored, existing_kwargs={"max_budget": 10.0},
+ payload={"allow_team_member_budget_overflow": enabled},
+ )
+ assert write.call_args.kwargs["data"]["metadata"] == {**stored, "allow_team_member_budget_overflow": enabled}
+ assert "allow_team_member_budget_overflow" not in write.call_args.kwargs["data"]
+ assert recorded[0].metadata == {"cost_center": "budget-test", "allow_team_member_budget_overflow": enabled}
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("kind", ["post", "patch"])
+async def test_team_overflow_can_disable_while_removing_hard_cap(kind: str) -> None:
+ _, write = await _drive_team_write(
+ kind, existing_metadata={"allow_team_member_budget_overflow": True},
+ existing_kwargs={"max_budget": 10.0},
+ payload={"max_budget": None, "allow_team_member_budget_overflow": False},
+ )
+ assert write.call_args.kwargs["data"]["max_budget"] is None
+ assert write.call_args.kwargs["data"]["metadata"] == {"allow_team_member_budget_overflow": False}
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("kind", ["post", "patch"])
+@pytest.mark.parametrize("payload", [
+ {"allow_team_member_budget_overflow": True},
+ {"metadata": {"allow_team_member_budget_overflow": True}},
+])
+async def test_team_overflow_cannot_be_enabled_without_hard_cap(kind: str, payload: dict[str, object]) -> None:
+ with pytest.raises(ProxyException) as error:
+ await _drive_team_write(kind, payload=payload)
+ assert str(error.value.code) == "400"
+ assert "finite positive team max_budget" in str(error.value.message)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("payload", [
+ {"allow_team_member_budget_overflow": True},
+ {"metadata": {"allow_team_member_budget_overflow": True}},
+])
+async def test_team_admin_cannot_enable_overflow_through_either_input(payload: dict[str, object]) -> None:
+ with _team_admin_may_edit("max_budget"):
+ with pytest.raises(ProxyException) as error:
+ await _drive_team_write(
+ "post", existing_kwargs={"max_budget": 10.0, "members_with_roles": [{"user_id": "member", "role": "admin"}]},
+ payload=payload,
+ user=UserAPIKeyAuth(user_id="member", user_role=LitellmUserRoles.INTERNAL_USER),
+ )
+ assert str(error.value.code) == "403"
+ assert "permission" in str(error.value.message)
+
+
+@pytest.mark.asyncio
+async def test_team_overflow_still_runs_custom_metadata_validator() -> None:
+ recorded: Final = []
+ with _configured_team_metadata_validator(_recording_validator(recorded, valid=False, error_message="cost center required")):
+ with pytest.raises(ProxyException) as error:
+ await _drive_team_write(
+ "post", existing_kwargs={"max_budget": 10.0}, payload={"allow_team_member_budget_overflow": True},
+ )
+ assert str(error.value.code) == "400"
+ assert "cost center required" in str(error.value.message)
+ assert recorded[0].metadata == {"allow_team_member_budget_overflow": True}
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("cap", [None, 0.0, -1.0, float("inf"), float("nan")])
+async def test_new_team_overflow_requires_finite_positive_hard_cap(mock_db_client, mock_admin_auth, cap: float | None) -> None:
+ from litellm.proxy._types import NewTeamRequest
+ from litellm.proxy.management_endpoints.team_endpoints import new_team
+
+ mock_db_client.db.litellm_teamtable.count = AsyncMock(return_value=0)
+ with pytest.raises(ProxyException) as error:
+ await new_team(
+ data=NewTeamRequest(max_budget=cap, allow_team_member_budget_overflow=True),
+ http_request=MagicMock(spec=Request), user_api_key_dict=mock_admin_auth,
+ )
+ assert str(error.value.code) == "400"
+ assert "max_budget" in str(error.value.message)
diff --git a/tests/test_litellm/proxy/spend_tracking/test_budget_reservation.py b/tests/test_litellm/proxy/spend_tracking/test_budget_reservation.py
index 9df8e6f4d67..9eee926a81c 100644
--- a/tests/test_litellm/proxy/spend_tracking/test_budget_reservation.py
+++ b/tests/test_litellm/proxy/spend_tracking/test_budget_reservation.py
@@ -342,3 +342,126 @@ async def test_release_unbound_budget_reservation_leaves_a_bound_one_to_its_call
assert spend_counter_cache.in_memory_cache.get_cache(key=counter_key) == pytest.approx(reservation["reserved_cost"])
assert reservation["finalized"] is False
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("enabled", [False, True])
+async def test_overflow_reserves_crossing_request_only_against_team_cap(
+ spend_counter_cache: DualCache, enabled: bool,
+) -> None:
+ cache: Final = UserApiKeyCache()
+ await cache.async_set_cache(
+ key=team_membership_reservation_cache_key(user_id="overflow-member", team_id="overflow-team"),
+ value=LiteLLM_TeamMembership(
+ user_id="overflow-member", team_id="overflow-team", spend=0.0,
+ litellm_budget_table=LiteLLM_BudgetTable(max_budget=1e-9),
+ ),
+ )
+ reserve: Final = reserve_budget_for_request(
+ request_body={"model": "gpt-6-astra", "input": "hello", "max_output_tokens": 10},
+ route="/v1/responses", llm_router=None,
+ valid_token=UserAPIKeyAuth(token="overflow-key", user_id="overflow-member", team_id="overflow-team"),
+ team_object=LiteLLM_TeamTable(
+ team_id="overflow-team", max_budget=10.0,
+ metadata={"allow_team_member_budget_overflow": enabled},
+ ),
+ user_object=LiteLLM_UserTable(user_id="overflow-member"), prisma_client=None,
+ user_api_key_cache=cache, proxy_logging_obj=ProxyLogging(user_api_key_cache=cache),
+ fail_closed_budget_enforcement=True,
+ )
+ if not enabled:
+ with pytest.raises(litellm.BudgetExceededError, match="TeamMember"):
+ await reserve
+ return
+ reservation: Final = await reserve
+ assert reservation is not None
+ assert reservation["reserved_cost"] > 1e-9
+ assert [entry["counter_key"] for entry in reservation["entries"]] == ["spend:team:overflow-team"]
+ assert spend_counter_cache.in_memory_cache.get_cache(key="spend:team:overflow-team") == reservation["reserved_cost"]
+ await release_unbound_budget_reservation(reservation)
+
+
+@pytest.mark.asyncio
+async def test_overflow_keeps_every_other_budget_reservation_guard() -> None:
+ from litellm.models.project import LiteLLM_ProjectTable
+ from litellm.proxy._types import LiteLLM_OrganizationTable
+ from litellm.proxy.common_utils.user_api_key_cache import project_cache_key
+ from litellm.proxy.spend_tracking.budget_reservation import _get_budget_counters
+
+ cache: Final = UserApiKeyCache()
+ await cache.async_set_cache(
+ key=team_membership_reservation_cache_key(user_id="overflow-member", team_id="overflow-team"),
+ value=LiteLLM_TeamMembership(
+ user_id="overflow-member", team_id="overflow-team", spend=1.0,
+ litellm_budget_table=LiteLLM_BudgetTable(max_budget=1.0),
+ ),
+ )
+ await cache.async_set_cache(
+ key="org_id:overflow-org:with_budget",
+ value=LiteLLM_OrganizationTable(
+ organization_id="overflow-org", budget_id="org-budget", created_by="admin", updated_by="admin",
+ litellm_budget_table=LiteLLM_BudgetTable(max_budget=20.0),
+ ),
+ )
+ await cache.async_set_cache(
+ key=project_cache_key("overflow-project"),
+ value=LiteLLM_ProjectTable(project_id="overflow-project", team_id="overflow-team", litellm_budget_table=LiteLLM_BudgetTable(max_budget=5.0)),
+ )
+ counters: Final = await _get_budget_counters(
+ request_body={},
+ valid_token=UserAPIKeyAuth(
+ token="overflow-key", user_id="overflow-member", team_id="overflow-team",
+ project_id="overflow-project", max_budget=2.0,
+ ),
+ team_object=LiteLLM_TeamTable(
+ team_id="overflow-team", organization_id="overflow-org", max_budget=10.0,
+ budget_limits=[{"budget_duration": "1d", "max_budget": 3.0}],
+ metadata={"allow_team_member_budget_overflow": True},
+ ),
+ user_object=LiteLLM_UserTable(user_id="overflow-member", max_budget=4.0),
+ prisma_client=None, user_api_key_cache=cache,
+ proxy_logging_obj=ProxyLogging(user_api_key_cache=cache), apply_user_budget_to_team_keys=True,
+ )
+ assert [(counter.entity_type, counter.max_budget) for counter in counters] == [
+ ("Key", 2.0), ("Team", 10.0), ("Team", 3.0), ("User", 4.0), ("Organization", 20.0), ("Project", 5.0),
+ ]
+
+
+@pytest.mark.asyncio
+async def test_overflow_requests_compete_for_remaining_team_reservation_capacity(spend_counter_cache: DualCache) -> None:
+ import asyncio
+
+ body: Final = {"model": "gpt-6-astra", "input": "hello", "max_output_tokens": 10}
+ estimated: Final = estimate_request_max_cost(request_body=body, route="/v1/responses", llm_router=None)
+ assert estimated > 0
+ cache: Final = UserApiKeyCache()
+ await cache.async_set_cache(
+ key=team_membership_reservation_cache_key(user_id="concurrent-member", team_id="concurrent-team"),
+ value=LiteLLM_TeamMembership(
+ user_id="concurrent-member", team_id="concurrent-team", spend=1.0,
+ litellm_budget_table=LiteLLM_BudgetTable(max_budget=1.0),
+ ),
+ )
+
+ async def reserve() -> object:
+ return await reserve_budget_for_request(
+ request_body=body, route="/v1/responses", llm_router=None,
+ valid_token=UserAPIKeyAuth(token="concurrent-key", user_id="concurrent-member", team_id="concurrent-team"),
+ team_object=LiteLLM_TeamTable(
+ team_id="concurrent-team", max_budget=estimated * 1.5,
+ metadata={"allow_team_member_budget_overflow": True},
+ ),
+ user_object=LiteLLM_UserTable(user_id="concurrent-member"), prisma_client=None,
+ user_api_key_cache=cache, proxy_logging_obj=ProxyLogging(user_api_key_cache=cache),
+ fail_closed_budget_enforcement=True,
+ )
+
+ results: Final = await asyncio.gather(reserve(), reserve(), return_exceptions=True)
+ admitted: Final = tuple(result for result in results if isinstance(result, dict))
+ rejected: Final = tuple(result for result in results if isinstance(result, litellm.BudgetExceededError))
+ assert len(admitted) == 1
+ assert len(rejected) == 1
+ assert "Team=concurrent-team" in str(rejected[0])
+ assert spend_counter_cache.in_memory_cache.get_cache(key="spend:team:concurrent-team") == pytest.approx(estimated)
+ await release_unbound_budget_reservation(admitted[0])
+ assert spend_counter_cache.in_memory_cache.get_cache(key="spend:team:concurrent-team") == pytest.approx(0.0)
diff --git a/ui/litellm-dashboard/src/components/team/TeamInfo.test.tsx b/ui/litellm-dashboard/src/components/team/TeamInfo.test.tsx
index 18b1505e342..d48a5d59bb5 100644
--- a/ui/litellm-dashboard/src/components/team/TeamInfo.test.tsx
+++ b/ui/litellm-dashboard/src/components/team/TeamInfo.test.tsx
@@ -2162,6 +2162,7 @@ describe("TeamInfoView - the exact bytes the update call sends", () => {
const wireBody = (payload: Record