LiteLLM proxy budgets let you hand a teammate, a script or a customer-facing app its own key with its own spending cap, so one runaway loop cannot eat the month's credits. The setup is a Postgres database, a master key, one team, a few members with max_budget_in_team, and a virtual key per person. Below I show a config I linted, provisioning payloads generated by a script I ran, and the traps in LiteLLM's own docs that are easy to miss.
I read LiteLLM's budgets, team budgets, virtual keys, spend tracking and spend log retention pages on 2026-10-09. I did not deploy LiteLLM or send a request through it for this post, so treat the API calls as unrun and check them against the docs for the version you install. This post builds on the base stack in Open WebUI with Ollama or vLLM behind a LiteLLM gateway; start there if you do not have the gateway running yet.
What do LiteLLM budgets actually cover?
The budgets page lists four levels: personal budgets (a key without a team_id), team budgets, team member budgets, and agent budgets. There are also budgets for customers (end users you pass in the user field of a request) and, on the global proxy, a single max_budget. The rules that surprise people:
- A key in a team uses the team's rules. If the key has a
team_id, only team and team-member budgets are enforced; the owner's personal budget does not apply. - Budgets need a database. On a deployment without one, the global
max_budgetfails open, meaning requests keep going past the limit. - A key with no
max_budgetis unchecked. By defaultmax_budgetis null, and the page says it is not checked for keys. - Windows reset on a schedule. Set
budget_durationsuch as30d. By default the server checks for resets every 10 minutes, so a reset can lag by that much. The reset-times page says daily, weekly and monthly budgets reset at midnight in the configured timezone (UTC if none is set), and that30dmeans the 1st of each month, not 30 days from creation. - Some per-model budgets are Enterprise. The page marks per-model budgets on keys and on internal users as enterprise features. I would not build a plan on them without checking your license.
What does a spend-control config look like?
This is the config I used. It extends the two-model file from the gateway post with budgets, key defaults and spend log retention. One model is local Ollama and one is Claude through an OpenAI-compatible gateway (more on that below):
model_list:
- model_name: chat-small
litellm_params:
model: ollama_chat/llama3.1:8b
api_base: http://ollama:11434
input_cost_per_token: 0
output_cost_per_token: 0
- model_name: claude-sonnet
litellm_params:
model: openai/anthropic/claude-sonnet-5-5
api_base: https://api.aicredits.in/v1
api_key: os.environ/AICREDITS_API_KEY
model_info:
input_cost_per_token: 0.0000022
output_cost_per_token: 0.000011
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
database_url: os.environ/DATABASE_URL
maximum_spend_logs_retention_period: "30d"
disable_fallbacks_on_per_model_rate_limits: true
litellm_settings:
timezone: "Asia/Kolkata"
budget_exceeded_status_code: 429
default_key_generate_params:
max_budget: 5.0
budget_duration: "30d"
duration: "90d"
upperbound_key_generate_params:
max_budget: 50.0
budget_duration: "30d"
key_alias_pattern: "^[a-z0-9]+(-[a-z0-9]+)*$"
I wrote a small checker that parses the YAML and tests the things this post relies on: master key from an environment variable, a database present, spend log retention set, every model priced or explicitly free, and defaults not above the upper bound. Output:
model chat-small base=http://ollama:11434 free (budget checks skipped)
model claude-sonnet base=https://api.aicredits.in/v1 priced
budget_exceeded_status_code: 429
OK
That is a lint, not proof LiteLLM accepts the file. What each setting does, according to the docs:
default_key_generate_paramsfillsmax_budget,durationand other fields when a/key/generaterequest leaves them out. With it set, a key made with an empty body still gets a $5 cap. This is the single best guard against an accidental unlimited key.upperbound_key_generate_paramsrejects requests above the bound with HTTP 400 (values are not clamped) and also acts as a default for fields left out.key_alias_patternforces readable, consistent aliases such assupport-bot-asha, which makes spend logs much easier to read.budget_exceeded_status_code: 429changes the code returned when a team budget is exceeded; the team budgets page says the default there is 422.disable_fallbacks_on_per_model_rate_limits: truematters only if you use per-model rate limits plus fallbacks. By default, a request over a key's per-model limit is retried on the fallback model and the client gets a 200. This setting makes the limit a hard 429.
How do you create a team, members and keys?
The sequence in the docs is: create the team, add members with a per-member budget, then create a key for each member that carries both user_id and team_id. I generated the payloads from a script so the numbers add up and the field names are linted. Real output, abridged:
1) POST /team/new
{
"team_alias": "support-bot",
"max_budget": 40.0,
"budget_duration": "30d",
"models": ["chat-small", "claude-sonnet"],
"rpm_limit": 120
}
member budgets add up to $35.00 against a team cap of $40.00
headroom: $5.00 unallocated
2) POST /team/member_add (one per person; team_id comes from step 1)
{"team_id": "<team_id from step 1>", "max_budget_in_team": 15.0, "member": {"role": "admin", "user_id": "asha"}}
{"team_id": "<team_id from step 1>", "max_budget_in_team": 10.0, "member": {"role": "user", "user_id": "ravi"}}
{"team_id": "<team_id from step 1>", "max_budget_in_team": 10.0, "member": {"role": "user", "user_id": "meera"}}
3) POST /key/generate (one per person; the key inherits team and member budgets)
{"user_id": "asha", "team_id": "<team_id from step 1>", "key_alias": "support-bot-asha", "duration": "90d", "rpm_limit": 30}
...
Lint: OK
Broken example:
- key/generate(bad): duration='thirty days' is not a duration string like 30d
- key/generate(bad): unexpected field max_budgit
Send each payload with the master key, as the docs show:
curl -X POST 'https://api.yourdomain.com/team/new' \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-H 'Content-Type: application/json' \
-d '{"team_alias": "support-bot", "max_budget": 40.0, "budget_duration": "30d", "rpm_limit": 120}'
Unrun. The response contains the team_id you paste into the next calls. Three notes from the docs:
- Spend is tracked for every team member, including those with no budget. If you add a budget later, spend already accrued counts against it immediately. The page gives the example of a member who spent $500 with no budget, then got a $100 cap and was rejected on the next request. If the new budget has a
budget_duration, they unblock at the next reset; otherwise someone must reset their spend. - To change a member's cap, call
/team/member_update. The page says the new budget takes effect on the member's next request, and that a limited-time bump usestemp_budget_increasewithtemp_budget_expiry. - Rotating a key:
/key/{key}/regenerateaccepts agrace_period, so the old and new keys both work during cutover. Scheduled rotation needs the database and an environment variable to turn the worker on. Check the virtual keys page for the exact variable names.
How do you cap a runaway day, not just a month?
A 30-day cap lets a bad Tuesday burn the whole month. The budgets page documents multiple budget windows on one key, via budget_limits:
{
"budget_limits": [
{"budget_duration": "24h", "max_budget": 10},
{"budget_duration": "30d", "max_budget": 100}
]
}
Each window is tracked and reset independently; the page says 24h resets daily at midnight UTC, which is 5:30 in the morning in India. LiteLLM's budget reset page documents a timezone setting under litellm_settings (it lists Asia/Kolkata), which I included in the config above so that "daily" means Indian midnight; I have not run it, so confirm the reset time on a test key before you tell a team the cap resets "every day". This pairs well with an agent-level guard in your own code; see token budgeting for agents for a loop guard I ran on a stub.
Also from the docs: budget reservation is on by default. Before sending a request upstream, LiteLLM estimates its maximum cost, using max_tokens if present and otherwise the model's configured limits, and reserves that amount. Two consequences. Set max_tokens on requests, or a key close to its cap can be rejected for a worst-case estimate it would never actually spend. And batch submissions cannot be priced up front, because the request holds a file ID, so LiteLLM records the cost when the batch completes. For budgets to be a hard ceiling across several proxy replicas, the docs describe fail_closed_budget_enforcement, which checks the database instead of trusting a possibly stale Redis counter; with it on, a request whose spend cannot be verified gets a 503.
What are the rate limit options?
Besides money, the same page covers requests and tokens per minute (rpm_limit, tpm_limit), max_parallel_requests, and per-model limits for keys and teams (model_rpm_limit, model_tpm_limit). For tokens-per-minute limits LiteLLM reserves output tokens up front, so requests without max_tokens can be throttled by a guess; the docs suggest declaring default_estimated_output_tokens sized to your observed p95. Verify a limit by calling with the key and reading the x-litellm-key-remaining-requests-<model> response headers the page describes.
Why do local models never trip a budget?
Because LiteLLM skips budget checks for models with explicit zero costs. The budgets page says requests to models configured with input_cost_per_token: 0 and output_cost_per_token: 0 are still allowed when a key is at its limit, and calls this "Fallback to free models". Good news: you can let a key that hit its Claude cap keep chatting with the local 8B model. Bad news: local models do not count toward anyone's cap, so a person can run unlimited Ollama requests that do cost you CPU time and RAM. The fix is rate limits (rpm, parallel requests) on those keys, not budgets.
The reverse mistake is leaving a priced model unpriced. The spend tracking page says a request that carries usage but prices to $0 is logged with spend 0, a warning names the missing pricing key, and a Prometheus counter litellm_zero_cost_requests_total lets you alert on it. If you route to a model name that is not in LiteLLM's price map, as is likely with a gateway's prefixed names, set the costs in model_info as in the config above.
How do you read spend afterwards?
All of these come from the LiteLLM docs; I did not query a live server:
- Each request writes a row to
LiteLLM_SpendLogswith the hashed key, user, team, model group, token counts and spend. The response headerx-litellm-response-costcarries the calculated cost. GET /spend/logs/v2returns paginated logs with filters such asapi_key,user_id,team_id,model. The older/spend/logstruncates to the 10,000 most recent rows, so use v2 for exports.GET /global/spend/report?start_date=...&end_date=...&group_by=teamgives a per-team report;group_by=customerand a per-key filter also exist. The docs mark spend reports as an Enterprise feature, so on the open-source build rely on/spend/logs/v2and the database.- Pass the
userfield on requests to attribute spend to your own end users. Request tags for spend by tag are marked Enterprise on the page. /key/info?key=...shows the key'sspendand limits, and/spend/keysand/spend/userslist spend but are scoped by role since recent versions: non-admin keys see only their own rows.
One housekeeping item that is easy to skip: spend logs grow forever by default. The retention page says the cleanup job only exists if you set a period such as maximum_spend_logs_retention_period: "30d", which I included in the config. At high volume it recommends Postgres partitioning because deleting rows leaves dead tuples. For a small team, a retention setting and a regular database dump (the keys live in that database, so the backup is also your key store) is enough.
What changes for India: rupees, markups and invoices?
LiteLLM tracks budgets in US dollars. For a rupee budget, convert yourself: at the Rs 95.7 per USD figure quoted in our AICredits review (September 2026, not a live rate), a $40 team cap is Rs 3,828 and a $10 member cap is Rs 957, as the script printed. Pick caps in dollars that map to rupee numbers your finance person recognises.
The second issue is markup. If you reach Claude through a gateway that bills in INR with a margin, LiteLLM's spend estimate comes from its price map, not from that invoice. My config prices Sonnet 5.5 at $2.20 and $11 per million tokens, which is Anthropic's $2 and $10 with the roughly 10 percent markup our AICredits review describes. The markup figure is from that review, not re-verified, and the LiteLLM docs list features for custom pricing and fee margins that I did not read in detail, so check them before copying my approach.
If you do not have a card that works for Anthropic or OpenAI billing, one practical route is an OpenAI-compatible gateway as the upstream. Some links on this page are affiliate links; they cost you nothing and I only link tools I'd mention anyway. AICredits takes UPI and bills in INR; LiteLLM's docs say to prefix OpenAI-compatible endpoints with openai/ and set api_base, which is how the config above is written. The downsides are real: the markup, credits that expire (one year, per our review), a middleman between you and the model vendor, and model IDs that you must copy from the gateway's own catalogue (the anthropic/claude-sonnet-5-5 name in my config is an assumption, so check it). Alternatives are a direct vendor account, OpenRouter, or a cloud marketplace route. If you already have direct billing, skip the middleman and point LiteLLM at the vendor. GST and invoicing questions belong with your accountant.
Where this setup is weak
- Per-person caps are soft. Spend is recorded after the call, and reservation estimates the worst case, so a key can pass its cap by a small amount under concurrency. The docs say batches may also overshoot.
- Local models are not metered by money. Use rate limits.
- Master key is the keys to everything. Keep it off Open WebUI and off laptops; give Open WebUI its own limited virtual key, as the gateway post shows.
- Enterprise-gated features. Per-model budgets and tag spend are marked as Enterprise on the docs pages I read.
- I did not run any of it. Test with a key capped at a tiny budget first: make a call, make another, and confirm the second is rejected with the status code you set.
For the cost levers that make the budget go further, see the Claude API bill checklist and, for routing between models behind the same gateway, LiteLLM and OpenRouter routing.



