If you sell AI features and bill your customers for usage, a budget limit is a promise about your margins. We wanted to know how well that promise holds when things get messy: parallel calls, oversized responses, backend outages, restarts, and hundreds of customers at once. So we tested two tools, LiteLLM, the popular open-source proxy, and Revenium, a commercial AI cost platform. The short answer: configured well, LiteLLM held every budget we set to within about 2.5%, even through a database outage. But several of its safest behaviors are off by default, and on the default setup we saw spend go unrecorded, budgets handed back after a restart, and customers slipping one call past their limit. Revenium's accounting was accurate to a fraction of a cent, but its enforcement works from a copy of the rules it refreshes on a timer, so overshoot grew with how fast a customer was spending.
The full set of tests cost under $2 in OpenAI usage. Here's what we did and what we found.
How we tested #
We wrote a small Python harness that sends calls to gpt-4o-mini on behalf of test customers, each with its own spend limit, and logs every call's cost, latency and whether it was blocked. Most tests used a $0.05 budget per customer, small enough to cross in about 35 calls. Every test started with a fresh customer at $0, and a stop-loss in the harness aborted any run that reached three times its budget.
- LiteLLM ran as a self-hosted proxy with Postgres, using per-customer
max_budgetlimits. We tested its default setup, then with its fail-closed setting, then with Redis. - Revenium ran through its Python SDK with a live budget rule: total cost, $0.05 per day, grouped by customer, with blocking turned on. We tested its default 60-second rule refresh and a 10-second refresh.
The tests:
- Calls one at a time until the customer is blocked.
- A 20-call burst , fired at once after spending 90% of the budget.
- One large call near the limit, allowed up to 16,000 output tokens.
- A cancelled stream , to see whether partial output is counted.
- Backend outages: stopping LiteLLM's database, or cutting Revenium's connection, partway through a run.
- 250 customers at once, each with a tiny budget, to see how limits behave at the scale of a real SaaS product.
Results at a glance #
| Overshoot past a $0.05 budget, by test | ||
|---|---|---|
| Test | Tool | Overshoot past budget |
| --- | --- | --- |
| Calls one at a time | LiteLLM | 2.4% |
| Calls one at a time, 60 s refresh | Revenium | 117% |
| Calls one at a time, 10 s refresh | Revenium | 14.5% |
| 20-call burst | LiteLLM | 2.5% |
| 20-call burst, 10 s refresh | Revenium | 51% |
| One large call near the limit | LiteLLM | 15.6% |
| One large call near the limit | Revenium | 4.8% |
| Cut off while under budget | Revenium | at least 198% |
Two things don't fit in a chart. Revenium's recorded spend matched our own tally to within $0.00004 in normal operation, so its accounting is solid; its overshoot comes from timing. And with Redis and its fail-closed setting turned on, LiteLLM's default-setup gaps closed: the large call was rejected before it reached OpenAI, and nothing was lost in an outage.
What held up #
LiteLLM handled concurrency better than we expected. We predicted a 20-call burst would sail past the budget, since each call would check the same counter before any of them finished. Instead, only 4 of the 20 got through. Reading the source explains why: before forwarding a call, LiteLLM reserves its worst-case cost (input tokens plus max_tokens) against the customer's budget, and every new call sees the reservations of calls still in flight. When a call finishes, the reservation settles to the real cost. A cancelled stream was settled to its input cost, which is close to what it actually cost.
LiteLLM's fail-closed setting stops oversized calls. With fail_closed_budget_enforcement: true, a call whose worst-case cost didn't fit the remaining budget was rejected in 25 ms, before it reached OpenAI.
LiteLLM with Redis survived every outage we tried. With REDIS_HOST/ REDIS_PORT set and use_redis_transaction_buffer: true, spend recorded while Postgres was down was re-queued and written once it came back, and a customer over budget stayed blocked after a proxy restart. With 250 customers active at once, every one was blocked right on schedule.
The proxy is cheap in latency. LiteLLM added about 28 ms at the median and 114 ms at the 95th percentile, small next to the model's own response time.
Revenium's books were accurate. In normal operation, its recorded spend matched ours to within $0.00004, and once it blocked a customer, it kept blocking them even after we cut its connection.
Where limits slipped #
LiteLLM, on its default setup
- Oversized calls get through. When a call's worst-case cost doesn't fit the remaining budget, LiteLLM shrinks the reservation to whatever is left and lets the call through. Our 16,000-token call at 95% of budget ended 15.6% over.
- An outage can lose spend for good. With Postgres stopped, LiteLLM kept blocking from its in-memory total, but its batched spend writes failed and were dropped after a few retries. Once Postgres came back, it had recorded $0.0196 of $0.0512 actually spent.
- A restart can hand budget back. After a proxy restart, LiteLLM reloaded the lower database figure, and a customer who had really spent past their limit was allowed through again.
- Hundreds of customers strain the counter cache. LiteLLM's spend counter cache holds 200 entries (issue #40221 ). With 250 customers active, 98 of them, the oldest entries in the cache, each got one extra call past their budget. The effect was small in our test, but it grows with how far the database lags behind real spend.
All four disappeared with Redis and the fail-closed setting turned on. Several related LiteLLM issues are open, including #35529, #34238 and #43732; we haven't reproduced those.
Revenium
- Enforcement lags by one rule refresh. The SDK decides locally, in under a millisecond, using a copy of the rules it refreshes on a timer. In our tests the block landed about one refresh interval after spend crossed the limit, so overshoot works out to roughly spending rate times refresh interval. At the 60-second default, an agent spending $1 a second could run about $60 past its limit, however large the budget.
- Bursts aren't held back. All 20 calls in the burst went through, because each one checked the same cached copy.
- A disconnected SDK can't start blocking. When we cut its connection while a customer was under budget, it never blocked them; spend reached three times the budget before our stop-loss ended the run. Usage reports wait in a memory-only buffer, so they arrive once the connection returns but are lost if the process exits first.
- Two setup traps. New rules start in shadow mode, which only records would-be blocks. And with SDK 0.12.0 on Revenium's cloud API, enforcement only worked once we set
REVENIUM_ENFORCEMENT_BASE_URL=https://api.revenium.ai/profitstream. The SDK's README lists this value but marks it optional, and the default produces a rules URL that returns 404. When that happened, every call was allowed and the SDK logged a warning that the rules couldn't be refreshed, without mentioning the 404; the cause showed only at debug level. We configured from the SDK source and missed the README line. Revenium pointed out that the setting is documented in the SDK README but marked optional, and is updating it to be more clear for future installers. Revenium then fixed this in SDK 0.12.1, released October 31, 2026, which also updates the README to document the default address. We confirmed the fix: the default address now includes/profitstream, an address that answers 404 is retried once with/profitstreamadded or removed, and a rules fetch refused with a 401, 403 or 404 is logged at WARNING with the URL and status.
If you run one of these today #
LiteLLM
- Run Redis and set
REDIS_HOSTandREDIS_PORT, so spend counters survive a proxy restart and aren't limited by the in-memory cache. - Set
general_settings: use_redis_transaction_buffer: true, so spend writes that fail during a database outage are retried instead of dropped. - Turn on persistence in Redis itself, since it's now part of your accounting.
- Set
general_settings: fail_closed_budget_enforcement: trueif you'd rather reject a call than let it run past the budget. - Handle HTTP 422 as a budget block in your app. LiteLLM returns 422 "ExceededBudget", not 429, so retry logic built for rate limits won't catch it.
- Always set
max_tokens. LiteLLM's reservation uses it as the worst case.
Revenium
- Use Revenium Python SDK 0.12.1 or later, which addresses the enforcement URL. On 0.12.0, set
REVENIUM_ENFORCEMENT_BASE_URL=https://api.revenium.ai/profitstream. - After creating a rule, turn off shadow mode and confirm the summary says "Block (live)".
- Check that blocking actually works with a throwaway customer and a tiny budget before relying on it.
- Consider
REVENIUM_CB_FAIL_MODE=closedwhile testing, so a missing or unreachable rules endpoint blocks calls instead of allowing them. - Lower
REVENIUM_CB_POLL_INTERVAL_SECONDSif overshoot matters more than refresh traffic. At 10 seconds our overshoot fell from 117% to 14.5%. - Set
REVENIUM_SHUTDOWN_TIMEOUT_SECONDSso buffered usage has time to send when a process exits. - Treat Revenium's limits as soft for fast-moving agents or bursts, and add a cap of your own, such as a per-run call or token limit, where that matters.
Caveats #
- Tiny budgets inflate percentages. A $0.05 budget makes a few cents of overshoot look dramatic. For Revenium, the dollar figure, roughly spending rate times refresh interval, is the one to carry over to your own setup.
- Mostly single runs. Each scenario ran once or twice on one machine with one model. Treat the numbers as directional.
- Specific versions. Results are for LiteLLM v1.106.6 and Revenium Python SDK 0.12.0. Revenium's SDK 0.12.1 fixes the address issue described above; the rest of the Revenium behavior we describe is unchanged in 0.12.1. Both projects ship often, and other details may change.
- One way to cut a connection. Our first Revenium outage test changed the SDK's address, which wiped its rule cache in a way a real outage wouldn't. We reran it by routing traffic through a proxy we could switch off, and report only the rerun.
- We told the vendors first. We ran these tests on October 29, 2026, and reported the SDK 0.12.0 setup issue to Revenium on October 30, with steps to reproduce and logs. Their support team replied the same day, and the summary of their response above is in words they approved. They shipped a fix in SDK 0.12.1 the next day.
- We're not neutral. We're exploring a product in this space, described below. The harness isavailable on GitHub , and the raw results are available on request.
Check your own setup #
We turned these tests into a free, open-source tool, stipend-check. Point it at your LiteLLM proxy and in about two minutes it reports which safe settings are off and runs a small live test with a throwaway customer, capped at five cents. For Revenium it checks your settings and that the SDK can actually read your rules, with no spend. Get stipend-check on GitHub.
What we're building #
The tests left us with a bigger question. Enforcement is the last step; before it, a company billing customers for AI usage needs to know what each customer actually costs, which customers are unprofitable or abusing a free tier, and whether its pricing makes sense. That's what we're working on, under the name Stipend.
If you sell AI usage to your customers and any of this sounds familiar, we'd like to hear how you handle it today. We're looking for a few design partners to try early versions and shape what we build.