Set up spending caps for your customers
Give every one of your customers a dollar line of their own, on Team and above.
On this page
If you sell AI usage inside your own product on a plan with included usage, you have a customer who costs more than they pay. This page gives every one of your customers a dollar line of their own. Three steps and a check, about ten minutes, top to bottom.
"Customer" on this page means your customer: the people and companies who use your product. Your own account with us is your organization.
What you will have at the end: every customer gets, say, five dollars a month; the accounts that pay you more get their own higher line; when a customer reaches theirs, their next request is refused before your AI provider is called, and every other customer keeps working.
What it needs: the Team plan or above, and the Owner or Admin role in your organization. On Free your organization keeps the count of customers you tagged this month and what they cost in total; setting, raising or arming a cap on a customer is Team and above.
Two things work beside it from the first day:
- The Breaker. A run your agent starts can carry its own budget in request headers, on every plan, Free included: the run is refused at its own line and the rest of your traffic keeps going (Breaker).
- The warning before the line. On Team and above, an alert mails you when your organization's spend or a key's spend crosses a percentage you set on its cap (Alert thresholds). For one of your customers the warning is on the wire instead: every response served under that customer's armed cap carries what is left, so your code can warn that customer at any percentage you choose (On a 200).
Step 1 · Send your requests through Recovea
You install Recovea beside the provider client you already use. The client keeps its own code; its base URL becomes Recovea's and its key becomes your Recovea key, and the provider key you connected in the platform is the one your requests are signed with:
from openai import OpenAI
client = OpenAI(
api_key=RECOVEA_KEY,
base_url="https://api.recovea.ai/v1",
)Everything else about the request is forwarded as you sent it. The quickstart has the whole first request.
Step 2 · Put the customer's id on each request
One extra header, carrying your own id for the customer; their id in your database is the usual answer:
client.chat.completions.create(
model=model,
messages=messages,
extra_headers={"x-recovea-tags": f"customer:{customer_id}"},
)Per call, never on the client. A client built once with
extra_headers={"x-recovea-tags": "customer:8812"} bills every customer you have
to customer 8812, because the customer changes from one request to the next.
The same header for curl, OpenAI TypeScript, the Anthropic SDKs and the Vercel AI SDK is in Customer spending caps · Send the tag, per stack. The platform prints the same six under Tag a request with a customer: in the connect steps, with the key you just minted filled into the curl one, and on the Limits page while no customer cap is set.
Three rules about the value, and they are the whole of it: 1 to 128 characters,
no commas, and one customer: tag per request. Case and spaces count, so send it
normalized the same way every time. Tags is the header's
own page.
Step 3 · Set the caps
Settings → Organization → Limits, the Customer spending caps section. It opens like this:
Customer spending caps
A cap on your customers, each in its own counter: a default rule for every
customer you tag on each clock you set, and an override for any one of them.
Where two default rules cover the same customer, the tightest decides.
No cap on every customer yet.
[ Set a cap for every customer ]
Overrides
No customer has a cap of its own.
[ Add a cap for one customer ]With nothing set yet the section also prints the header itself, under Tag a request with a customer, with the same six snippets step 2 links to.
3a · One cap for every customer
Set a cap for every customer opens the cap form: the amount and the window, the hard stop, and, below them, alert percentages and their recipients. On a customer cap those last two send no mail; the warning for a customer is the headroom header in On a 200. Set five dollars a month, turn the hard stop on, and save:
[Every customer] Every customer you tag
This cap is on every customer you tag, each in its own counter. A new
customer starts at zero the moment its first tagged request arrives.
Override it for the customers who pay more.
$5.00 Per month
Resets in 3 days · <the boundary in UTC> · <the same instant in your own clock>
Applies to every customer; spend is counted per customer
Counted over 2026-09: 1 of the 4 customers it covers spent $5.00 or more.
Armed for the whole period, this cap would have refused that customer.
[ ●───] Armed [ Edit ] DeleteThe reset line is the interval first, then the boundary in UTC, then the same instant in your own time zone. The last two lines are a readback: what this rule would have done over the period the figures cover, counted only over the customers it actually governs. A customer holding an armed override of its own is left out of it.
With the hard stop off, a cap counts and refuses nothing. That is the honest way to watch the number for a week before you enforce it. Armed, it refuses.
A new customer needs no setup: their count starts at zero with their first tagged request.
Under the cards the section prints N of 10,000 customers this month, a table of Customers this period (one row per customer with its spend, cap, what is left and a Stopped column), and a Find one customer box that answers on the whole id you type and nothing shorter.
3b · A higher cap for the customers who pay more
Add a cap for one customer asks for the window first, then the customer:
[ Per month ▾ ] [ Choose a customer… ▾ ] [ Continue ] [ Cancel ]
8812
4471
A customer we have not seen yetThe list holds the customers you have tagged that do not already have a cap on that clock. A customer we have not seen yet lets you type the value, which is how you cap a customer before their first request. Continue opens the same cap form on that customer: press it before you enter an amount, because a form that says This cap is on every customer you tag is the rule from 3a, and saving that sets the rule for every customer. Saved, the row reads:
Overrides
Customer 8812 · Per month · $50.00 · resets in 3 days · replaces the default while armed
Spend Cap $50.00 · modeled $12.40 (receipts, this window)
Hard stop [ ●───] ArmedThe resets clause is when this customer's counter closes, which is the question a reader looking at a stopped customer is asking; on a cap that never resets it says so instead. The modeled figure is the priced record: the provider's reported tokens priced at the price list, and the number to bill from.
While an override is armed it replaces the default rule for that customer on that window. A default rule on a different window still applies. A lower override works exactly the same way as a higher one.
The check · Did it work
Send one tagged request and read the reply's headers:
curl -i https://api.recovea.ai/v1/chat/completions \
-H "Authorization: Bearer $RECOVEA_KEY" \
-H "Content-Type: application/json" \
-H "x-recovea-tags: customer:8812" \
-d '{"model":"gpt-5-nano","messages":[{"role":"user","content":"hello"}]}'Four things should be true.
-
The reply carries the cap's state. Five header values, all five on a response served under an armed customer cap, four on a cap that never resets, which names no reset instant:
Textx-recovea-budget-scope: customer x-recovea-budget-window: month x-recovea-budget-remaining: 4.998740 x-recovea-budget-cap: 5.000000 x-recovea-budget-resets-at: 2026-11-01T00:00:00ZTwo headers instead of five (a scope that is not
customer, and no figures) means an armed cap of your own is standing over the request and this customer's cap is not: the tag did not arrive, or this customer has no armed cap. Nox-recovea-budget-*header at all means nothing armed was standing over it. Either way, start at troubleshooting. -
The customer appears. The platform's Dashboard counts them under Customers seen:
TextCustomers seen 1 1 customer seen this month, $0.000041 across them · added as each request is recorded; unpriced requests add nothingThe count is live off the record. The total is the sum of the customer cut's monthly rows other than
untagged, the row for requests with no customer tag, and each request is added to its row as it is recorded. A total under a cent prints to six decimals. A request that could not be priced adds nothing, so a non-zero count can sit beside a zero total, and that pair is not a fault in the tag. While nothing has been tagged the same tile reads "No tagged requests yet. Send x-recovea-tags: customer:<id>.", which is the reading that says the header has not arrived. -
The cap refuses when it is reached. Set one customer's override to a few cents, send requests until it trips, and read the refusal:
TextHTTP/1.1 402 Payment Required x-recovea-budget-scope: customer x-recovea-budget-window: month x-recovea-budget-resets-at: 2026-11-01T00:00:00Z {"error":{"message":"Monthly budget reached for this customer. Traffic resumes at reset, or raise the cap.","type":"insufficient_quota","param":null,"code":"budget_exceeded"}}Then send a request for a different customer on the same key: it is served. Put the override back when you are done. A raise goes out on a five-second tick, and in a two-node test on a local estate the gateway applied it 9 to 24 milliseconds after that tick, so that customer is served again within about five seconds.
-
The requests are attributable. The Requests page filters by customer tag value; a value that sent nothing in the period answers "No requests from this customer this period." rather than an empty table.
What your app should do
On a 402
- Read
error.codeandx-recovea-budget-scopetogether.budget_exceededwithcustomermeans that customer's included usage ended. Any other scope is a cap of your own;kill_switch_onis the kill switch, andcustomer_cardinality_exceededis the bound on new customers (the wire).budget_unverifiablenever arrives on this status (On a 503). - Tell that customer their included usage has ended, and offer them the upgrade.
x-recovea-budget-resets-atis when it comes back, in UTC. - Do not retry. It is refused again until the window resets or you raise the cap, every retry still counts against your key's request rate, and each one is recorded as a refused request. On Free the refusals also end once your organization's month reaches the Free ceiling, when every cap turns observe-only (On Free).
- If you plan to let that customer buy more and continue, hold the request
yourself. We refuse before your provider is called and keep nothing of the
refused request (no body, no queue, no replay), so there is nothing for us to
send on once you raise the cap. Keep it in your own backend and re-send it
then. The steadier shape is not to be refused at all: watch
x-recovea-budget-remainingon every200and stop on your own side before the line. - Optionally cache
customer → resets-aton that first refusal and answer that customer from your own cache until the instant passes. A refusal can be followed by a served request while our nodes converge: in a two-node test on a local estate on 2026-09-12, between 0.7 and 5.0 seconds, about 2.8 at the median over twenty runs, and rarely well over a minute when two nodes publish in the same instant. Treating the first refusal as that customer's state is faster and steadier than asking us again, and every one of those waits errs toward serving, never toward refusing early.
On a 503
budget_unverifiablewithRetry-After: 1means we could not confirm the spend and refused rather than guess. Retry it. The official OpenAI and Anthropic SDKs retry a503by default, so with them your customer sees a slightly slower response instead of a refusal; a bare HTTP client retries only if you have told it to, after theRetry-Afterseconds.- Never show your customer a quota message for it. It is not their usage ending, and a message saying it was is wrong the moment the retry succeeds.
On a 200
x-recovea-budget-remainingis what is left for that customer, in dollars, to six decimals. It rides a response served under an armed customer cap and no other, so read it besidex-recovea-budget-scope: customer.1 − remaining/capis the share used, so warn your customer at whatever percentage you choose: there is no threshold you have to agree with us about.- Treat
remainingas the most that is left, never the least: it is counted after this request is admitted and before anything still running for the same customer is priced.0.000000on a successful response means the next request for this customer is refused, and it is a rare reading rather than the one to wait for, so act on a small figure. - Do not forward these headers to a browser or a customer-facing client. They are for your server.
And one thing not to expect. We never email you when one of your customers is refused, and we never contact your customers at all. Each refusal is kept in the record, and the Stopped column of Customers this period on Limits shows when a cap last stopped that customer.
What to expect from the numbers
A cap stops the next request, not the ones already running, so a customer's total can end a little above their cap: for one large request on an expensive model, several dollars. The reasons, the arithmetic and what to do if you need a hard number are on Customer spending caps.
Next
- Customer spending caps: how it works, in plain words: the counter, the windows, the tag, the bound.
- Customer spending caps: the wire: the exact shapes.
- Customer spending caps: troubleshooting: when it does not do what you expected.