From a46c0168172aad8d0adca35a39333bcf5d4e0a58 Mon Sep 17 00:00:00 2001 From: inference-bot Date: Mon, 14 Sep 2026 10:46:47 -0600 Subject: [PATCH] Add /broker/usage (spend vs balance) + fix broker to use member balance --- app/main.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/app/main.py b/app/main.py index 59f4157..f1e1df7 100644 --- a/app/main.py +++ b/app/main.py @@ -718,7 +718,8 @@ async def broker_create_api_key(email: str, name: str) -> dict: if not name or not re.fullmatch(r"[A-Za-z0-9._-]{1,64}", name): raise HTTPException(400, "Invalid key name (1-64 chars, alphanumeric/._-)") - team_id = await litellm_get_or_create_team(email, MEMBER_BUDGET) + balance = get_member_balance(email) + team_id = await litellm_get_or_create_team(email, balance) alias = _api_key_alias(email, name) async with httpx.AsyncClient() as client: r = await client.post( @@ -1140,6 +1141,38 @@ async def broker_revoke(name: str, request: Request): return {"status": "revoked", "name": name} +async def broker_get_usage(email: str) -> dict: + """Return the member's spend vs. balance, from their team. + + LiteLLM's team object exposes `spend` and `max_budget` directly, so we read + the member's team and return {balance, spend, remaining}. This is the + read-only primitive the member dashboard uses (no master key on the client). + """ + team_id = await litellm_get_or_create_team(email, get_member_balance(email)) + async with httpx.AsyncClient() as client: + r = await client.get( + f"{LITELLM_BASE}/team/list", + headers={"Authorization": f"Bearer {LITELLM_MASTER_KEY}"}, + ) + r.raise_for_status() + for t in r.json(): + if t.get("team_id") == team_id: + balance = float(t.get("max_budget") or get_member_balance(email)) + spend = float(t.get("spend") or 0.0) + return { + "balance": balance, + "spend": spend, + "remaining": max(balance - spend, 0.0), + } + return {"balance": get_member_balance(email), "spend": 0.0, "remaining": get_member_balance(email)} + + +@app.get("/broker/usage") +async def broker_usage(request: Request): + email = _verify_broker_secret(request) + return await broker_get_usage(email) + + async def reconcile_memberships() -> dict: """Reconcile the portal against the live Open Collective member list.