Contents
Figure 1: LLM Token Limits: The Settings That Actually Cut Spend — every request crosses the gateway, so every token can be counted there
Your model bill climbs, and nobody can say why. One team sends 8,000-token system prompts. Another retries failed calls in a tight loop. A third asks the same question 400 times a day.
An LLM gateway sits in the perfect spot to fix all of that. This guide explains how to set token usage limits and optimize token spend with an LLM gateway, using LiteLLM's current docs as the concrete example. The ideas carry over to any gateway that supports limits, caching and routing.
Why the Gateway Is the Right Place for Token Control
Every request passes through the gateway, so it sees tokens that app code never reports. It can count them, cap them, cache them and reroute them before the provider bills you. Doing this inside each app means fixing the same problem in ten codebases.
| Control | What it does | Where it lives |
|---|---|---|
| Limits | Stops any key, team or user from consuming more than their share | tpm_limit, rpm_limit, max_parallel_requests, max_budget [1] |
| Reduction | Cuts the number of tokens you send and receive | max_tokens on requests, shorter prompts, caching |
| Routing | Sends each request to the cheapest model that can handle it | Model aliases, fallbacks, budget fallbacks [5] |
Start with limits, because they protect you while you work on the rest.
Step 1: Measure Before You Optimize
You cannot trim what you cannot see. LiteLLM tracks spend per key, team and user when you run it with a database, and its responses include headers such as x-litellm-key-remaining-tokens-{model} and x-litellm-key-remaining-requests-{model} for per-model limits [1].
Pull a week of data and answer four questions:
- Which keys and teams spend the most?
- Which models eat the budget?
- How many input tokens does a typical request carry, versus output tokens?
- Which prompts repeat?
In many setups, a handful of keys account for most of the spend. Go after those first.
This works best when each app has its own virtual key, so attribution stays clean. If you have not set that up yet, the virtual keys and budgets guide covers the key-per-app pattern.
Step 2: Set Token Limits That Protect You
Limits keep one runaway job from hurting everyone else. LiteLLM supports tpm_limit, rpm_limit and max_parallel_requests on keys, users and teams [1].
curl --location 'http://localhost:4000/key/generate' \
--header 'Authorization: Bearer <your-master-key>' \
--header 'Content-Type: application/json' \
--data '{"tpm_limit": 50000, "rpm_limit": 60, "max_parallel_requests": 10}'
Several details from the docs decide whether these numbers hold [1][2]:
| Setting | What it changes |
|---|---|
token_rate_limit_type |
TPM counts total tokens by default; set input or output to count one side only [2] |
model_tpm_limit, model_rpm_limit |
Per-model ceilings on a key, team, org or project, so an expensive model gets a tighter cap [1] |
disable_fallbacks_on_per_model_rate_limits |
Returns 429 on a per-model limit instead of retrying a fallback model [2] |
fail_closed_rate_limit_enforcement |
Rejects with 503 when Redis counters are unreachable, instead of enforcing per instance from memory [2] |
global_max_parallel_requests |
Caps concurrency across the whole proxy, complementing the per-deployment max_parallel_requests [2] |
The Redis point is the one that quietly breaks limits. Without shared counters, each replica counts separately, so a 50,000 TPM limit becomes 50,000 times your replica count. That is not a limit, it is a suggestion with extra steps.
Give the Gateway Better Guesses About Output Size
TPM limits work by reserving tokens before a call and reconciling after it. When a request omits max_tokens, the gateway has to guess how much output to reserve.
LiteLLM lets you declare that guess with default_estimated_output_tokens and a per-model map, default_estimated_output_tokens_per_model, on a key or a team [1]:
curl --location 'http://0.0.0.0:4000/key/generate' \
--header "Authorization: Bearer $LITELLM_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"tpm_limit": 50000,
"default_estimated_output_tokens": 600,
"default_estimated_output_tokens_per_model": {"gpt-reasoning": 4000}
}'
The docs advise sizing the estimate near the 95th percentile of what the model actually emits, not its maximum context [1]. Both directions have a cost:
- Declare too high and the over-reservation blocks requests the budget could have served.
- Declare too low and concurrent requests are admitted against an under-reservation, so the window overruns the limit once they finish.
The docs are explicit that the second failure is worse [1].
There is a second reservation model for the newer per-direction limits. The beta itpm/otpm deployment limits enforce input and output tokens per minute separately, and they resolve an "effective output cap" in a fixed order: the request's max_tokens or max_completion_tokens, then the model's max_output_tokens, then a hard default of 4096 [4]. A request with no max_tokens against a 32,768-output model reserves 32,768 tokens of headroom before it runs [4]. That single missing parameter is why concurrency collapses on some deployments.
Add Dollar Budgets on Top
Token limits control speed, and budgets control total spend. Pair them.
A key can carry a max_budget with a budget_duration, and the docs describe stacking several budget windows, such as a daily cap plus a monthly cap [1]:
curl 'http://0.0.0.0:4000/key/generate' \
--header 'Authorization: Bearer <your-master-key>' \
--header 'Content-Type: application/json' \
--data '{
"max_budget": 100,
"budget_duration": "30d",
"budget_limits": [
{"budget_duration": "24h", "max_budget": 10},
{"budget_duration": "30d", "max_budget": 100}
]
}'
The reasoning in the docs is worth repeating: a single window cannot stop a bad day from burning your whole month [1].
Two operational catches:
- Budgets need a database, or they silently do nothing. No spend rows, no enforcement.
- Per-model key budgets (
model_max_budget) are an Enterprise feature, so the budget fallback chain built on top of them needs that licence too [1][5].
Budget reservation also leans on max_tokens. LiteLLM estimates a request's maximum cost, reserves it and rejects the call up front if the reservation exceeds the budget [1]. A request with a sensible max_tokens reserves less, so it fails less often.
Step 3: Cap Output Tokens
Output tokens usually cost more than input tokens, and models love to ramble. Set max_tokens (or max_completion_tokens) on every request that has a reasonable ceiling. A summarizer that never needs more than 300 tokens should never receive permission to produce 4,000.
The gateway can help enforce this in three ways:
- Teach your teams to set the parameter, and lint for it in code review.
- Use the key-level estimate settings above so the reservation matches reality.
- Watch for prompts that ask for long, formatted answers when a short answer would do.
This is also the cheapest change on the list. No architecture, no new service, one parameter per call site.
Step 4: Reduce Input Tokens
Input waste hides in plain sight. Look for these patterns in your logs:
| Pattern | Symptom in the logs | Fix |
|---|---|---|
| Bloated system prompts | A prompt that grew by accretion over six months | Rewrite it; it often halves without losing quality |
| Unbounded chat history | Input tokens growing every turn | Trim or summarise old turns |
| Repeated context | The same documents attached to every request | Reference them once, or cache the prefix |
| Verbose tool definitions | Dozens of schemas sent on calls that use none | Send only the tools that call needs |
The gateway cannot rewrite your prompts for you, but it can show you the numbers. Sort requests by input size and fix the top offenders first.
LiteLLM's docs also describe prompt compression through a tool called Headroom, which I did not test, so evaluate it carefully before you rely on it [3].
Step 5: Cache What Repeats
The cheapest token is the one you never send. LiteLLM caches responses at the gateway, and the docs list several backends [3]:
| Type | Backend | Use when |
|---|---|---|
redis |
Redis, Valkey, ElastiCache, Memorystore | Default for anything past a single worker |
redis-semantic, valkey-semantic, qdrant-semantic |
Vector similarity | Single-shot prompts with paraphrased repeats |
s3, gcs |
Object storage | Large, cold cache you can share |
local, disk |
In-process or on-disk | Local development only |
litellm_settings:
cache: true
cache_params:
type: redis
Set the connection through REDIS_URL, or REDIS_HOST, REDIS_PORT and REDIS_PASSWORD [3]. The proxy exposes /cache/ping to confirm the cache works [3].
The multi-worker trap: an in-memory cache lives inside one worker process, so four workers keep four separate caches and the hit rate drops roughly by the worker count [3]. Use Redis once you run more than one worker or replica.
Exact-Match Caching
Exact-match caching returns a stored answer when a request matches a previous one. It fits deterministic workloads: classification, extraction and FAQ-style questions. It misses when anything in the request changes, so small prompt tweaks reduce your hit rate.
Semantic Caching
Semantic caching embeds the prompt and serves the closest stored answer whose similarity clears a threshold [3].
litellm_settings:
cache: true
cache_params:
type: redis-semantic
similarity_threshold: 0.8
redis_semantic_cache_embedding_model: my-embedding-model
This feature has sharp edges, and the docs say so plainly [3]:
- A semantic cache embeds the text of the entire messages array, system prompt included.
- On multi-turn or agentic traffic, each turn is the previous request plus a small append, so it can replay stale responses. Raising the threshold does not reliably fix this.
- Assistant
tool_callsare not part of the embedded text, which makes consecutive agent turns even harder to tell apart. - The recommended fix is exclusion: set
"cache": {"no-cache": true}in the metadata of the keys your agents use, or make caching opt-in withmode: default_off. - The embedding call runs inline before the request reaches the model, capped at 5 seconds. Past the deadline the lookup is abandoned, the response carries
x-litellm-semantic-similarity: 0.0, and the request proceeds as a cache miss.
Start with exact-match caching on safe workloads. Add semantic caching only after you test a sample of its answers by hand.
Provider Prompt Caching
Providers such as OpenAI and Anthropic offer their own prompt caching, which discounts repeated prompt prefixes. LiteLLM's docs treat that as a separate feature from the gateway cache. Put stable content, like system instructions and reference documents, at the start of your prompts so the provider can reuse it. Check each provider's current rules and discounts, since they change.
Step 6: Route Easy Work to Cheaper Models
Not every request needs your best model. Classification, extraction and short rewrites often work fine on a small, cheap model. Define model aliases in the gateway, like fast-chat and smart-chat, and let each app pick the cheapest alias that passes its quality tests.
Then add safety nets [1][5]:
| Mechanism | What it does | Caveat |
|---|---|---|
| Router fallbacks | Sends traffic to a backup model when the first fails or hits a limit | enforce_fallback_model_access defaults to true: the key must be allowed to call the fallback model [2] |
| Budget fallbacks | Reroutes to the next model in a per-key chain when model_max_budget is exhausted |
Needs the Enterprise per-model budget, and only fires on that check [5] |
enforce_fallback_budget |
Defaults to true, so fallbacks stop once the key is out of budget | Set false to let fallbacks run anyway [2] |
Budget fallbacks are more subtle than they look. They only apply to the per-key model_max_budget check inside the auth layer, spend is attributed to the fallback model, and router-level fallbacks keep running separately on provider errors [5]. If every fallback in the chain is over budget, the original BudgetExceededError surfaces [5].
This is where the load balancing guide pays off: routing decisions and spend decisions are the same decisions.
Step 7: Control Retries and Failure Behavior
Retries burn tokens quietly. A client that retries a long, failing request five times pays for five attempts if the provider bills partial usage.
- Set sensible
num_retriesandrequest_timeoutvalues at the gateway, not in every client. - Teach clients to back off after a 429 instead of hammering the endpoint.
- Test what happens when a provider returns errors, since cost surprises often hide in failure paths.
- Watch the retry path against your per-model limits, because fallbacks can turn one rejected request into a paid call on another model.
The timeout side of this deserves its own settings pass — the HTTP timeouts and connection tuning guide covers the values that stop a hung provider from holding a slot.
A Simple Optimization Loop
Run this loop every month:
- Measure: rank keys, models and prompts by token spend.
- Pick the biggest target: fix the top one, not the easiest one.
- Change one thing: cap output, shorten a prompt, add a cache or switch a model.
- Check quality: compare answers before and after, using a fixed test set.
- Measure again: confirm the savings in real data.
Quality checks matter most. A cheaper answer that users hate costs more in the end.
Common Pitfalls
- Setting budgets without a database. The limits never fire.
- Skipping
max_tokens. Output length drifts upward and nobody notices, and the reservation model inflates every concurrency check. - Turning on semantic caching for chat agents. Stale replays follow.
- Running multiple gateway instances without Redis. Your effective limit multiplies by the instance count.
- Optimizing without a quality test set. You save tokens and lose users.
- Declaring output estimates far above real usage. You throttle traffic you could serve.
- Forgetting that per-model key budgets are Enterprise. The budget fallback chain never fires on the free tier.
- Assuming a fallback respects your per-model cap. By default it does not; set
disable_fallbacks_on_per_model_rate_limits.
Best Book to Read
| Cover | Book | Description | Get it |
|---|---|---|---|
![]() |
Accelerate: The Science of Lean Software and DevOps | the discipline behind the loop in this post: measure, change one thing, prove it with data instead of vibes. | View on Amazon |
As an Amazon Associate I earn from qualifying purchases. Links on this page are affiliate links, and I may earn a commission at no extra cost to you.
Wrapping Up
Gateways give you three levers for token control: limits to protect you, reduction to shrink what you send, and routing to pay less per request. Measure first, set limits second, then cap outputs, cache what repeats and route easy work to cheaper models. Test every change against real answers.
The failure modes repeat across teams: no database behind the budgets, no max_tokens on the calls, semantic caching on agents, and Redis missing across replicas. Fix those four and most bill surprises disappear.
So pull one week of usage by key today and find your single biggest spender. Fixing that one key will probably teach you more than any general advice, including this guide.
If you are still choosing a gateway, the LiteLLM vs OpenRouter vs Portkey comparison covers which of these controls you get on each platform.
Frequently Asked Questions
How do I set a token limit per API key in LiteLLM?
Send tpm_limit, rpm_limit and max_parallel_requests to /key/generate, or set the same fields on a team or user so the limit survives key rotation. Add model_tpm_limit and model_rpm_limit to give an expensive model a tighter ceiling than a cheap one. In a multi-replica deployment these counters must live in Redis, otherwise each instance counts separately and your real limit multiplies by the instance count.
Why do my LiteLLM budgets not block anything?
Budgets need a database. Without one, the proxy cannot track spend and the limit fails open. Once a database is connected, max_budget with a budget_duration resets on a schedule, and budget_limits lets you stack several windows, such as a daily cap plus a monthly cap on the same key. Check that the key actually has a budget and that the spend row is being written before you trust the cap.
What is default_estimated_output_tokens in LiteLLM?
It is the output size the gateway assumes when a request does not declare max_tokens, used to reserve token-per-minute headroom before the call and reconcile after it. You can set one value, or a per-model map with default_estimated_output_tokens_per_model, on a key or a team. Size it near the 95th percentile of what the model actually emits: too high throttles traffic you could have served, and too low admits concurrent requests against an under-reservation that overruns the limit.
Is semantic caching safe for chat agents in LiteLLM?
The docs say no, and they say it plainly. A semantic cache embeds the text of the entire messages array, system prompt included, and serves the closest cached response above the similarity threshold. On multi-turn or agentic traffic each turn is the previous request plus a small append, so it replays stale responses. Keep it for single-shot prompts, exclude agent traffic with cache.no-cache metadata on those keys, or use exact-match caching instead.
Does LiteLLM caching work across multiple workers?
Not with the in-memory cache: it lives inside one worker process, so four workers keep four separate caches and the hit rate drops roughly by the worker count. Use Redis or Valkey once you run more than one worker or replica. The docs list redis, valkey, redis-semantic, valkey-semantic, qdrant-semantic, s3, gcs, local and disk as cache types, and the proxy exposes /cache/ping to prove the backend is reachable.
How do I stop fallbacks from bypassing per-model rate limits?
Set disable_fallbacks_on_per_model_rate_limits: true in general_settings. By default a request that exceeds a key, team, organisation or project per-model limit can retry on a router fallback, which spends money on a different model instead of returning 429. The flag makes that per-model check a hard cap. Other local limits, such as a key's overall rpm_limit, keep their existing fallback behaviour.
Sources
[1]LiteLLM, Budgets and rate limits for keys, teams and users —tpm_limit,rpm_limitandmax_parallel_requestson keys, users and teams,model_rpm_limitandmodel_tpm_limitper-model ceilings, thex-litellm-key-remaining-tokens-{model}andx-litellm-key-remaining-requests-{model}response headers,default_estimated_output_tokensanddefault_estimated_output_tokens_per_modelon keys and teams with the 95th-percentile guidance and both over- and under-reservation failure modes,max_budgetwithbudget_duration, the stackedbudget_limitswindows with the "$10/day and $100/month" example, budget reservation using the request's maximum cost, andmodel_max_budgetas an Enterprise feature: docs.litellm.ai/docs/proxy/users[2]LiteLLM, Config settings —token_rate_limit_typewith itstotaldefault andinput/outputoptions,disable_fallbacks_on_per_model_rate_limitsand the note that other local limits keep their fallback behaviour,fail_closed_rate_limit_enforcementreturning 503 when Redis counters are unreachable,max_parallel_requestsandglobal_max_parallel_requests,enforce_fallback_model_accessdefaulting to true, andenforce_fallback_budgetdefaulting to true: docs.litellm.ai/docs/proxy/config_settings[3]LiteLLM, Caching — the cache type table (redis, valkey, redis-semantic, valkey-semantic, qdrant-semantic, s3, gcs, local, disk), the warning that an in-memory cache is per worker so hit rate drops by the worker count, the/cache/pingendpoint,REDIS_URLand theREDIS_HOST/REDIS_PORT/REDIS_PASSWORDvariables, semantic caching's embedding of the whole messages array, the multi-turn and agentic staleness warning,cache.no-cachekey metadata andmode: default_offexclusion, the 5-second inline embedding cap with thex-litellm-semantic-similarity: 0.0fallback, and the Headroom prompt compression page: docs.litellm.ai/docs/proxy/caching, docs.litellm.ai/docs/proxy/caching_semantic and docs.litellm.ai/docs/proxy/headroom[4]LiteLLM, Separate ITPM / OTPM rate limits (beta) — per-deploymentitpmandotpmwith theenforce_model_rate_limitspre-call check, the reservation-then-reconcile model, the effective output cap order (requestmax_tokens/max_completion_tokens, then modelmax_output_tokens, then a 4096 default), the worked example reserving 32,780 tokens for a 150-token answer, ITPM refunds on failure with no OTPM charge, cached prompt tokens excluded after reconciliation, and thex-ratelimit-*-input-tokens/x-ratelimit-*-output-tokensheaders: docs.litellm.ai/docs/proxy/io_token_rate_limits[5]LiteLLM, Budget fallbacks (v1.92.0+) — rerouting on a key'smodel_max_budgetinstead of returningbudget_exceeded, thebudget_fallbacksmap keyed by primary model, spend attributed to the fallback model, router-level fallbacks staying independent, andBudgetExceededErrorwhen every fallback is over budget: docs.litellm.ai/docs/proxy/budget_fallbacks
