Skip to Content
GatewaysLiteLLMBudgets & spend

LiteLLM budgets: set limits and verify request blocking

LiteLLM budget not working? Configure max_budget on the virtual key your app actually uses, then run a bounded test that proves requests are rejected for budget reasons — not just displayed in the admin UI.

Jump to the verification example (local network_mock stack, no paid provider by default) or the troubleshooting table.

Hub: Tokenminning with LiteLLM.

This page answers litellm max_budget, litellm budget exceeded, and litellm budget not working in one place.

Budgets vs rate limits vs alerts

ControlWhat it blocksWhere to configureProof signal
Financial budget (max_budget, team/user budgets)Requests when recorded + reserved spend would exceed a dollar capVirtual key, team, user, or global litellm_settings.max_budgetExceededBudget / ExceededTokenBudget / budget_exceeded
Rate limit (rpm_limit, tpm_limit)Throughput (requests or tokens per minute)Key, team, user, modelrate_limit / TPM or RPM messages — not dollar spend
Soft budget / alertsNothing by itself — notifications onlyCallbacks, dashboards, external monitoringAlert fired but requests still return 200

A failed request alone does not prove budget enforcement. Match the error shape and correlate with /key/info spend.

Where should I configure the budget?

For most applications, put the cap on the virtual key the service uses for inference — not the proxy master_key.

ScopeWhen to useEnforced against
Virtual key (/key/generate → max_budget)One app, CI job, or environmentThat key’s spend
Team (team_id on the key)Shared pool across teammatesTeam budget; key-owner personal budget does not apply
Internal user (user_id on the key)Multiple keys per engineerUser-level spend across their keys
Global proxy (litellm_settings.max_budget)Whole-deployment ceilingAll traffic on the proxy
End-user / customer (user field + max_end_user_budget_id)SaaS metering per customerCustomer id across keys

Team scope details: LiteLLM proxy users .

Requirements (documented): budgets need Postgres. Without a database, virtual keys cannot resolve and no key/team/user budget is enforced. Global max_budget fails open without a DB. See LiteLLM budget docs .

Redis: budget reservation and cross-pod spend counters use Redis in multi-worker deployments. See budget reservation . A single local container can run with Redis for realistic concurrency behavior.

How do I create a budgeted virtual key and inspect its spend?

  1. Deploy the proxy with Postgres — see proxy setup.
  2. Generate a dedicated test key (keep the master key admin-only):
curl -X POST 'http://localhost:4000/key/generate' \ -H 'Authorization: Bearer sk-<master-key>' \ -H 'Content-Type: application/json' \ -d '{ "max_budget": 0.01, "budget_duration": "30d", "models": ["gpt-4o-mini"], "key_alias": "app-dev", "metadata": {"env": "dev"} }'
  1. Inspect spend and limits before and after test traffic:
curl 'http://localhost:4000/key/info?key=sk-<virtual-key>' \ -H 'Authorization: Bearer sk-<master-key>'
FieldMeaning
max_budgetDollar cap for the current budget window
spendRecorded spend LiteLLM uses for enforcement
budget_durationReset window (30d, 24h, 30s, …)
budget_reset_atNext scheduled reset (when duration is set)
modelsAllowlist — requests to other models fail before budget is checked

On successful completions, check x-litellm-response-cost in response headers and aggregate reports:

curl 'http://localhost:4000/global/spend/report?start_date=2026-01-01&end_date=2026-01-31&group_by=team' \ -H 'Authorization: Bearer sk-<master-key>'

Tag requests for cost attribution

Pass tags in the request body for feature-level slicing (does not replace key budgets):

response = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": "Hello"}], user="jane_smith", extra_body={"metadata": {"tags": ["feature:code-review", "env:prod"]}}, )

Warning: Clients can set user freely. For backend services, issue per-user virtual keys with user_id at creation — do not rely on self-declared user for enforcement.

How do I verify that LiteLLM rejects a request because of the budget?

Central question: how do I prove this budget is enforced, rather than merely displayed?

Evidence that counts

CheckPass signal
HTTP status + JSON bodyExceededTokenBudget, ExceededBudget, or error.type: "budget_exceeded"
/key/info before/afterspend at or above max_budget (or admin lowered max_budget below spend)
Proxy logsBudget check logged on the rejecting worker
Not sufficientGeneric 401, provider 429, or upstream 5xx without budget fields

Example rejection shapes from virtual key budgets :

{ "detail": "Authentication Error, ExceededTokenBudget: Current spend for token: 7.2e-05; Max Budget for Token: 2e-07" }
{ "error": { "message": "ExceededBudget: Key over budget. Spend=0.01, Budget=0.001", "type": "budget_exceeded", "code": "400" } }

Minimal manual proof (one allowed, one blocked)

Do not burn a large production budget to test blocking. Either:

  • Use a tiny test key on a disposable proxy, or
  • Lower max_budget below recorded spend with /key/update to simulate exhaustion without extra provider traffic.
# After at least one successful request recorded spend: curl -X POST 'http://localhost:4000/key/update' \ -H 'Authorization: Bearer sk-<master-key>' \ -H 'Content-Type: application/json' \ -d '{"key": "sk-<virtual-key>", "max_budget": 0.000001}' curl -X POST 'http://localhost:4000/chat/completions' \ -H 'Authorization: Bearer sk-<virtual-key>' \ -H 'Content-Type: application/json' \ -d '{ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "test"}], "max_tokens": 16, "stream": false }'

That proves the already-exhausted path. Use the verification script for concurrent near-limit behavior.

What changes when requests run concurrently?

LiteLLM 1.100.0 enables budget reservation by default. See budget reservation :

  1. Estimate max cost from the request body (max_tokens) and model pricing.
  2. Reserve that amount against the budget before calling the provider.
  3. Reject early if the reservation would exceed the cap.
  4. Replace the reservation with actual cost after the response is priced.
TermMeaning
Recorded spendValue in Postgres / /key/info after reconciliation
ReservationTemporary hold for in-flight requests
Final request costActual priced tokens (may differ from the estimate)

Near the limit, some concurrent workers succeed and others return budget_exceeded — order is not guaranteed. A single concurrency test does not prove zero overspend under all load patterns.

general_settings.disable_budget_reservation: true removes pre-request holds; concurrent requests can exceed the cap until spend is recorded (config ). LiteLLM logs a warning while this is enabled.

Why might requests be blocked before reported spend reaches the limit?

CauseWhat to inspectFix
Reservation > actualRequest omits max_tokens; proxy uses a large default estimateSet explicit max_tokens / max_completion_tokens on client requests
Team or user budgetKey has team_id — team budget applies, not key max_budget aloneCheck /team/info, team-member max_budget_in_team
Model allowlistError before budget checkAlign models on the key with the requested model name
Per-model budget (Enterprise)/key/info → model_max_budget_usageEnterprise per-model caps (docs )
Multiple budget windowsbudget_limits with daily + monthly capsInspect each window in /key/info
Stale reservationRequests fail after earlier calls completedWait for reconciliation; avoid disable_budget_reservation unless mitigating a known issue

Why might requests continue when I expected them to stop?

CauseWhat to inspectFix
No databaseStartup warning; keys not resolvedAdd Postgres database_url — proxy setup
Bypassing the proxyApp calls provider directlyRoute all inference through the proxy URL
Zero-cost modelinput_cost_per_token and output_cost_per_token both explicitly 0Budget checks are skipped for zero-cost models (docs )
Unpriced / wrong model mapCost falls back to 0 for unknown models (regression fixed in recent releases)Pin model names; set explicit model_info pricing
Redis counter stale after restartSpend in DB higher than Redis hot counterEnable fail_closed_budget_enforcement for hard ceilings (docs )
Reservation disableddisable_budget_reservation: trueRe-enable reservation for concurrent safety
Wrong keyMultiple keys in envConfirm the app’s Authorization bearer matches the budgeted key

How do I restore access after a legitimate budget reset or adjustment?

Normal recovery — raise the cap or wait for the reset window, not disable enforcement:

# Temporary increase (documented on virtual keys) curl -X POST 'http://localhost:4000/key/update' \ -H 'Authorization: Bearer sk-<master-key>' \ -H 'Content-Type: application/json' \ -d '{ "key": "sk-<virtual-key>", "max_budget": 5.0, "temp_budget_increase": 2.0, "temp_budget_expiry": "7d" }'

Check budget_reset_at in /key/info when budget_duration is set. Scheduled resets run on an internal scheduler (default check interval ~10 minutes; tunable via proxy_budget_rescheduler_min_time / max_time — reset docs ).

Admin spend reset endpoints exist for correcting accounting errors; prefer budget adjustment for routine recovery. After a reset, verify with one allowed request and /key/info.

Troubleshooting: budget not working

SymptomEvidence to inspectLikely causeFixVerification
Spend shows in UI but requests never block/key/info spend > max_budget, requests still 200No DB on enforcement path, zero-cost model, or bypassing proxyAdd Postgres; confirm non-zero pricing; route all traffic through proxyLower max_budget below spend; expect budget_exceeded
401 Authentication Error onlyBody lacks ExceededBudget / ExceededTokenBudgetWrong or expired keyRegenerate key; fix client envValid key returns 200; invalid returns 401 without budget fields
budget_exceeded but spend looks lowConcurrent requests; reservationsReservation holds near limitSet explicit max_tokens; retest sequentiallySequential requests succeed until true spend exceeds cap
Blocked below max_budgetDebug logs mention reservationmax_tokens estimate too highReduce max_tokens or raise budget slightlyRequest with smaller max_tokens succeeds
Team key ignores personal capKey has team_idTeam budget scope (v1.95.0+ )Set max_budget on team or max_budget_in_team/team/info spend matches enforcement
Works on one pod, drifts on clusterMulti-replica deploy without RedisPer-pod spend driftAdd Redis; consider fail_closed_budget_enforcementConcurrent test from verification example
Provider bill >> LiteLLM spendCompare model names and datesWrong base_model / cost mapSet model_info.base_model and custom pricingx-litellm-response-cost matches expected per request

Enforcement limits (LiteLLM 1.100.0)

Documented behavior (sources ):

  • Budget checks read spend from the database; Redis holds the hot cross-pod counter.
  • Reservation is on by default for chat completions with token pricing.
  • fail_closed_budget_enforcement (off by default) rejects with 503 when spend cannot be verified in Redis or DB.
  • Batch jobs (POST /batches) cannot be fully reserved at submission — monitor completed batch cost separately.
  • Streaming, images, audio, unpriced models — not covered by the walkthrough below; verify separately before trusting caps.

Observed in the local verification example (network_mock, pinned image): virtual-key max_budget blocks chat completions with structured budget_exceeded / ExceededTokenBudget errors; concurrent near-limit admission matches reservation semantics above.

Not proven by mocks: network_mock exercises proxy accounting, not your provider’s invoice. Re-run against your real model before production sign-off.

Run the budget verification example

The repo ships a bounded, opt-in script with a disposable Docker Compose stack. Default mode uses network_mock — no paid provider calls.

PinValue
LiteLLM imagedocker.litellm.ai/berriai/litellm:main-v1.100.0-stable (1.100.0)
DatabasePostgres 16
Cache / reservationRedis 7
Modelbudget-test-model (input_cost_per_token: 0.000001, output_cost_per_token: 0.000002)
Request styleNon-streaming POST /chat/completions with max_tokens: 32

Files: public/examples/litellm-budget-verify/ in the wiki repo .

cd public/examples/litellm-budget-verify docker compose up -d export LITELLM_RUN_LIVE=1 # optional: LITELLM_BASE_URL=http://localhost:4000 # optional: LITELLM_MASTER_KEY=sk-litellm-budget-verify-master node verify.mjs docker compose down -v # cleanup

Download: verify.mjs  · helpers.mjs  · docker-compose.yml  · config.yaml 

Safety caps (enforced in script)

CapDefault
Inference requests≤ 10
Concurrent workers≤ 3
Retries0 (num_retries: 0 in config)
Paid providerDisabled unless you modify the example (not recommended)
Opt-inLITELLM_RUN_LIVE=1 required

Preflight prints a conservative cost estimate from token pricing assumptions. The script refuses to run without LITELLM_RUN_LIVE=1. CI and pnpm check never call LiteLLM or paid APIs.

What the four cases exercise

CaseActionExpected
A. AllowedCreate key, inspect /key/info, one request with headroom200, spend increases, x-litellm-response-cost present
B. RejectedAdmin lowers max_budget below recorded spendbudget_exceeded / ExceededTokenBudget — already-exhausted path
C. ConcurrentFresh key with ~1.4× single-request budget; 3 parallel workersMix of accept/reject; final spend reconciled
D. Recovery/key/update raises max_budgetNext request succeeds

Offline unit tests in lib/litellm-budget-verify.test.ts cover error classification and cost parsing only — they do not prove live proxy enforcement.

Wiring your own provider (optional)

To test against a real model, copy the example to an isolated proxy, set provider credentials via environment variables, remove network_mock, and keep the same safety caps. Expect provider charges; the stock example does not enable this path.

Spending guardrails (production)

  • Never share the master key with application code
  • Set max_budget on every virtual key; use models allowlist on CI keys
  • Set explicit max_tokens so reservations match real usage
  • Export weekly spend via /global/spend/report
  • Sync model pricing when provider rates change — cost tracking 
Last updated on