LiteLLM Brownout
Move a LiteLLM team's chosen routes to cheaper models on budget tiers
No budget runaway, no quick drain of a team budget. Give a route a fallback and a percentage, and the gateway switches that route once the team's spend passes it.
Example
Take a team with $500 a week. Its Q&A route falls back to a smaller model at 90%, and its code review route has no fallback.
| Team spend | Q&A route | Code review route | Signal |
|---|---|---|---|
| Below $375 | Primary | Primary | |
| $375 to $449.99 | Primary | Primary | 75% warning |
| $450 and above | Fallback | Primary | 90% warning and downgrade headers |
| Too little left for the next request | Refused | Refused | LiteLLM's 429 |
LiteLLM already reserves each request's cost, counts team spend across replicas and refuses requests past the budget. Brownout adds five things.
- Warnings at any percentage: each team and route sets its own tiers, logged once per budget window across replicas. LiteLLM's own budget alerts fire at 85% and 95%.
- Downgrades on the team's total: a route switches to its fallback at the percentage you set. LiteLLM's budget fallbacks switch only when a key exhausts a per-model budget.
- Coding agents included: chat, text completion, Anthropic's
/v1/messages, OpenAI's/v1/responsesand Google'sgenerateContentall downgrade, so a Claude Code or Codex session carries on with the fallback mid-task. - Checked fallbacks: brownout switches only to a fallback the gateway serves and prices, and that the caller's key and team may call.
- Disclosure:
x-brownout-*headers and the spend logs name the model that answered and why.
How it works
One manifest, brownout.yaml, holds every team's budget, tiers and fallbacks. It lives in your repo
next to the gateway config, so a policy change goes through code review, and brownout check runs
it in CI. LiteLLM keeps enforcing the budget, and brownout picks the model that serves each
request.
Requirements
Brownout governs every model call on the gateway, so the gateway needs a few things in place. init
checks each one and prints the fix.
- Every key that calls a model belongs to a team, and that team is in the manifest.
- Budgets sit on teams, never on keys.
- Clients use LiteLLM's unified routes. Provider pass-through routes, realtime, batches, fine-tuning and search tools are refused.
fail_closed_budget_enforcementis on, andcoordination_redispoints at a Redis.- LiteLLM is 1.101.6 to 1.106, pinned by image tag.
A gateway without Redis needs one that shares its REDIS_PASSWORD, such as this service.
services:
redis:
image: redis:7
command: ["sh", "-c", "exec redis-server --requirepass \"$$REDIS_PASSWORD\" --appendonly yes --maxmemory-policy noeviction"]
env_file:
- .env
Get started
1. Run init against your gateway. Paste the master key on the first line.
read -rs LITELLM_MASTER_KEY && typeset +x LITELLM_MASTER_KEY
BROWNOUT_INIT_KEY=$(curl -sSf http://localhost:4000/user/new \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" \
-d '{"user_id": "brownout-init", "user_role": "proxy_admin_viewer"}' | jq -er .key) \
&& export BROWNOUT_INIT_KEY
uvx --from litellm-brownout brownout init --gateway http://localhost:4000 --gateway-config config.yaml
2. Fix what it lists, rerun it with --write, then check the files.
uvx --from litellm-brownout brownout check --gateway-config brownout.config.yaml --manifest brownout.yaml
curl -sSf http://localhost:4000/user/delete \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" \
-d '{"user_ids": ["brownout-init"]}'
3. Build the gateway image with brownout and run it on the new files.
FROM docker.litellm.ai/berriai/litellm:v1.106.1
RUN python -m ensurepip --upgrade && python -m pip install --no-deps litellm-brownout==0.1.0
services:
litellm:
build: .
command: ["--config", "/app/brownout.config.yaml", "--port", "4000"]
environment:
BROWNOUT_MANIFEST: /app/brownout.yaml
volumes:
- ./brownout.config.yaml:/app/brownout.config.yaml:ro
- ./brownout.yaml:/app/brownout.yaml:ro
4. Send one request with a team's key.
curl -s -i http://localhost:4000/v1/chat/completions \
-H "Authorization: Bearer {team key}" -H "Content-Type: application/json" \
-d '{"model": "{model}", "messages": [{"role": "user", "content": "hi"}], "max_tokens": 20}' \
| grep -io '^x-brownout-[a-z-]*: [^[:cntrl:]]*\|"reason": *"[a-z_]*"'
What happened:
initread the gateway as a read-onlybrownout-inituser and listed every request brownout would refuse, with the fix for each. OpenRouter models need a price, which one command prints.--writecreatedbrownout.yaml, the manifest, andbrownout.config.yaml, your config with brownout's settings.checkvalidated both, and thebrownout-inituser was deleted.- The response names the model that answered in
x-brownout-route-served. A refused request prints its reason instead. The master key belongs to no team, so brownout refuses it.
Your first fallback
Add a route under your team in brownout.yaml. Fill in each {placeholder}.
teams:
"{team}":
routes:
"{route}":
fallback: "{fallback}"
degrade_at: 90
Add {fallback} to the team's model list in LiteLLM, run brownout check again and restart the
gateway. For models added in the admin UI, check reads the gateway with a viewer key.
BROWNOUT_INIT_KEY=$(curl -sSf http://localhost:4000/user/new \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" \
-d '{"user_id": "brownout-init", "user_role": "proxy_admin_viewer"}' | jq -er .key) \
&& export BROWNOUT_INIT_KEY
uvx --from litellm-brownout brownout check --gateway http://localhost:4000 --gateway-config brownout.config.yaml --manifest brownout.yaml
curl -sSf http://localhost:4000/user/delete \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" \
-d '{"user_ids": ["brownout-init"]}'
Once the team's spend passes 90%, every {route} response carries these headers.
x-brownout-route-served: {fallback}
x-brownout-budget-tier: 90
x-brownout-degraded-from: {route}
x-brownout-degradation-reason: degrade_tier_reached
The fallback receives everything the route sends, tools and images included, so try it on the route's real requests first. To point Claude Code or Codex at a route, follow their setup. The lab drains a budget without spending anything.
Limits
- Only text generation downgrades: embeddings, images, audio and Codex's
/v1/responses/compactkeep their model at any spend. - Requests name a model group: a router alias, a wildcard group such as
openai/*or a list of models gets a 503. - No router fallbacks: a provider error on the chosen model reaches the caller. Failover between deployments of one model group still works.
- The reservation comes first: LiteLLM reserves each request's worst case on the model the
caller asked for. Set
max_tokens, or a large request near the ceiling gets LiteLLM's 429 before the downgrade. - Redis is on the request path: while LiteLLM cannot reach Redis it refuses every budgeted request.
The full list covers the rest. If you run brownout on a real workload, open an issue with the model pair, budget window, threshold and LiteLLM version, and say whether the fallback stayed useful.
Documentation
- Configuration: settings, manifest keys, rules and
initoptions. - Enforcement: refusals, headers and events.
- Limits: every known limit.
- Claude Code and Codex: pointing both tools at a route.
- Design: how the modules fit together.
- Security: what brownout reads and writes, and how to verify a release.
- Contributing: the tests, the supported LiteLLM releases and releasing.
License
Metadata
Release files for litellm-brownout 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| litellm_brownout-0.1.0.tar.gz | 36.1 kB | With provenance | Details for litellm_brownout-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| litellm_brownout-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for litellm_brownout-0.1.0-py3-none-any.whl |
Total release size: 80.0 kB
Release files / litellm_brownout-0.1.0.tar.gz
| Download URL | litellm_brownout-0.1.0.tar.gz |
|---|---|
| Size | 36.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4beb521f66150b33e77fa26a1f3b4534b00f114cd56853c2cffd2cfdcef8549b
|
|
BLAKE2b-256 checksum How to use checksums |
45f2fcdd09e95625cd987d8b39562d9f64f8fc148685ae32e204152cf72eb8b0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logRelease files / litellm_brownout-0.1.0-py3-none-any.whl
| Download URL | litellm_brownout-0.1.0-py3-none-any.whl |
|---|---|
| Size | 43.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c9c52c47b90d1c0cb3244764175fddb59acb95a2d7034e76e8adf1a8138f66b4
|
|
BLAKE2b-256 checksum How to use checksums |
517e125cd1beec98f5a8bbbadcc4407729091c4409171710b2343a1e1172da8b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logSecurity for litellm-brownout 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for litellm_brownout-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.