# Per-customer AI usage limits: rate, token and spend caps for SaaS

URL: https://infro.io/blog/per-customer-ai-usage-limits
Author: INFRO Editorial (https://infro.io/blog/editorial)
Publisher: INFRO
Published: 2026-10-20
Last modified: 2026-10-20
Last reviewed: 2026-10-20 (next review due 2026-11-19)
Topic: Cost and FinOps
Format: Guide

> Limit AI usage per customer with three kinds of cap: a request rate (stops bursts and scripts), a token or unit quota (bounds volume) and a spend cap in dollars per day, week or month (bounds cost across models). Identify each customer with an opaque id on every request, check the cap before the provider is called, reserve each request's worst-case cost so concurrent requests cannot pass the cap together, and return a refusal that says which limit was hit and when it resets.

Key takeaways:
- Use three caps together: a request rate for bursts, a quota for volume, and a dollar spend cap for cost across models.
- Identify the customer with an opaque id on every request; never an email address or a name.
- Check the cap before the provider is called, and reserve each request's worst-case cost so concurrent requests cannot pass it together.
- A refusal should name the limit, the period and when it resets, so your app can tell the user something useful.
- Keep internal and batch traffic out of customer caps, and give plans different defaults with per-customer overrides.

**How do you limit AI usage per customer?** Give every customer three caps: a request rate that stops bursts, a quota that bounds volume, and a spend cap in dollars per period that bounds cost across every model they can reach. Check them before the provider is called, using an opaque customer id sent with each request. Two details decide whether the caps work in production: they must hold when many requests arrive at once, and the refusal must tell the user what happened.

Provider rate limits do not help here. OpenAI, Anthropic and Google limit your organization, project or key; none of them knows which of your customers sent a request. Per-customer limits are yours to build or to buy.

## Why per-customer limits matter

- **Margin.** A small group of heavy customers usually carries most of an AI feature's cost. Without a cap, a plan's worst customer can cost more than the plan earns. [AI feature unit economics](https://infro.io/blog/ai-feature-unit-economics) works through an example.
- **Abuse.** Free trials and free tiers attract scripts that use your product as a free proxy to a model. A per-customer cap bounds the damage of each account; a sign-up limit bounds the number of accounts.
- **Runaway automation.** A customer's integration in a retry loop, or an agent stuck re-running a step, spends at machine speed. A spend cap stops it in minutes rather than at the end of the month.
- **Fairness.** Your provider limits are shared. One customer's burst can starve everyone else unless each customer has a share.

## The three kinds of cap

| Cap | Unit | Stops | Weakness on its own |
| --- | --- | --- | --- |
| Request rate | Requests per second or minute | Bursts, scripts, retry storms | A few very large requests pass |
| Quota | Requests, tokens or units per period | Sustained volume | Cost varies widely by model and request size |
| Spend cap | Estimated dollars per day, week or month | Cost, across every model | Needs a price for every model the customer can reach |

Use all three. The rate limit is cheap and catches the fastest failures; the spend cap is what actually bounds your provider bill. A token quota is useful only where one model serves the feature and is unlikely to change: switch the feature from a small model to a large one and the same quota costs ten times as much.

## Identify the customer on every request

Every request needs the id of the customer it serves, decided by your backend, never by the client. Use an opaque id — a database key or a hash — and never an email address or a name, because the id will be stored with every request record.

Providers ask for the same kind of id for abuse detection. OpenAI recommends sending a `safety_identifier` and suggests hashing the username or email "to avoid sending us any identifying information"[1]. Anthropic's `metadata.user_id` should be "a uuid, hash value, or other opaque identifier", up to 512 characters, and must not include a name, email address or phone number[2]. Sending the same opaque id to the provider and to your own limiter keeps one identity across both.

Decide what happens to requests with **no** customer id: internal jobs, evaluations, a background summariser. Either route them through a separate key or project with its own budget, or refuse them at the boundary. A cap that silently skips anonymous requests is a cap with a hole in it.

## Check before the call, and survive concurrency

A cap checked after the provider call is a report, not a limit. The check belongs in front of the call, and it has to account for requests already in flight.

The naive version reads the customer's spend, compares it with the cap, and sends the request. Under load that fails: twenty requests read the same "$4.90 of $5.00 used", all pass, and the customer ends at $9. The fix is a **reservation**: atomically add the request's worst-case cost to the customer's in-flight total if, and only if, spend plus in-flight plus this request fits the cap; release the reservation and record the real cost when the request finishes.

```lua
-- KEYS[1] = spent total for this customer and period
-- KEYS[2] = in-flight reservations for this customer and period
-- ARGV[1] = cap, ARGV[2] = worst-case cost of this request (same unit, e.g. micro-dollars)
local spent    = tonumber(redis.call("GET", KEYS[1]) or "0")
local inflight = tonumber(redis.call("GET", KEYS[2]) or "0")
local cap, cost = tonumber(ARGV[1]), tonumber(ARGV[2])
if spent + inflight + cost > cap then
  return 0                                   -- refuse: no room
end
redis.call("INCRBY", KEYS[2], cost)          -- hold the worst case
return 1
```

Redis runs a script like this atomically, so no other command interleaves between the read and the increment[3]. After the response, decrement the reservation by the worst case and add the real cost to the spent total. Set an expiry on both keys at the end of the period so they reset on their own, and on Redis Cluster give both keys the same hash tag (`{cust_42}:spent`, `{cust_42}:inflight`) so the script can touch them together. The worst case is the input token count times the input rate plus the output limit times the output rate; [count tokens before you call the API](https://infro.io/blog/count-tokens-before-api-call) covers getting that number. The [500-request concurrency test](https://infro.io/blog/ai-spend-cap-concurrency-test) shows what this protects against.

## Periods, plans and overrides

- **Period.** Daily caps catch runaway automation fastest; monthly caps match how customers think about a plan. Many products use both: a monthly allowance and a daily ceiling at a few times the average day. State the reset time in UTC.
- **Plan defaults.** Each plan gets a default cap sized from its price and your target margin.
- **Overrides.** Enterprise contracts, trials and goodwill gestures need per-customer exceptions. Store them as overrides on a rule, not as a row per customer, so changing a plan's default changes everyone who has no override.
- **A zero cap** should mean "refuse every priced request", which is useful for suspended or unpaid accounts.

## The refusal your users see

When a customer hits a cap, your app needs enough information to say something useful. The refusal from your limiter should carry:

- which limit was hit (rate, quota or spend; daily or monthly),
- when it resets, as a timestamp,
- the customer id it applied to.

Turn that into a sentence in your product: "You've used this month's AI allowance on the Team plan. It resets on 1 November, or you can upgrade now." Return it as its own error type so the client never retries it; a retry loop against a spend cap is one more way to annoy the user.

## Alert before the wall

A cap that is hit without warning feels like an outage to the customer. Send an in-product notice at 80% of the allowance, and an internal alert when a customer reaches a cap for the first time, or several days in a row. The second is a sales signal as often as an abuse signal.

## Per-customer caps with INFRO

INFRO calls these end-customer caps. Your app sends its own opaque customer id in `metadata.user_id` or the `X-INFRO-User-Id` header. A rule sets a default spend cap per day, week or month for the organization or for one project, overrides change it for specific customers, and either the console or your backend through the management API sets them. Before a request reaches your provider, INFRO checks the customer's spend for the period plus the worst-case cost of their requests still in flight, and admits the request only if its own worst case fits; otherwise it answers `402` with `code: "end_user_budget"`, the period and `resets_at`. Requests with no customer id are served or refused by your choice. Caps are created on the Team plan and above; your organization, project, member and key budgets keep applying on top, and the spend counted is INFRO's estimate of what your provider will bill.

## Summary

Put a rate limit, a quota and a dollar cap on every customer, identified by an opaque id your backend sets. Enforce them before the provider call with reservations that hold under concurrency, give plans defaults and contracts overrides, and make the refusal say what was hit and when it resets.

## Questions

### How do I limit OpenAI API usage per user?

OpenAI's own limits apply to your organization and project, not to your users, so per-user limits are yours to enforce. Tag each request with an opaque user id, keep a counter per user for requests, tokens or estimated cost, check it before calling OpenAI, and refuse with a clear message when the user is over. A gateway can do the counting and refusing for you.

### Should I limit tokens or dollars per customer?

Dollars, if customers can reach more than one model. A token quota means very different costs on a small and a large model, and has to be rewritten whenever you change model. Keep a request rate limit beside the dollar cap to stop bursts.

### What should the user see when they hit an AI usage limit?

A message that says which limit they reached, for what period, and when it resets, plus what they can do now, such as upgrading or waiting. A generic error or a silent failure produces support tickets.

## Sources

1. Safety best practices — OpenAI. https://developers.openai.com/api/docs/guides/safety-best-practices (accessed 2026-10-20)
2. Create a Message — Anthropic. https://platform.claude.com/docs/en/api/messages/create (accessed 2026-10-20)
3. Scripting with Lua — Redis. https://redis.io/docs/latest/develop/programmability/eval-intro/ (accessed 2026-10-20)
