Cap
An armed cap answers 402 before your provider is called. On Free the caps protect up to one monthly total per organization; the kill switch stops outright.
On this page
Cap is a dollar limit on your spend that the gateway enforces in the request
path. Spend caps count each request at its priced cost once its response ends.
A request the gateway cannot price counts at its floor while it runs
(What a cap does not do).
Recovea's cost tracking is Meter, the budgets one run carries are
Breaker, and the receipts every priced request leaves are
Prove. An armed cap refuses the request before it reaches your
provider and answers 402. An alert only warns; a cap refuses.
What a cap is
A cap is an amount on a scope over a window:
- the scope is your whole organization, one API key, or one of your
customers, named per request in the
x-recovea-tagsheader; a cap on a customer is Team and above (Customer spending caps); - the window is a day, a week, a month, a year, or the lifetime of the cap, on UTC boundaries (Caps and windows);
- armed means its hard stop is on. A cap with the hard stop off counts spend and sends its alerts, and refuses nothing.
Caps are set in the platform, on Settings → Organization → Limits, and a key's cap also on that key's own row (Where a cap is set). The guide sets one up end to end, and Set up spending caps for your customers does the same for your customers.
What the request carries
For a cap on your organization or on a key, nothing: a cap is armed in the
platform and pushed to every gateway node, and the request carries only your
key. The same request is served one minute and refused the next because the
cap's counter moved, not because the request changed. A cap on one of your
customers also reads the customer the request names, as
x-recovea-tags: customer:<your id>.
What the gateway does
Before your provider is contacted, the gateway runs admission in order: it authenticates the key, refuses the request if the kill switch is on for the key or the organization, applies throttling, then checks every armed cap that covers the request: the organization's, the key's and, on a request that names a customer, that customer's. For each cap it adds a floor for this request to the spend already counted in the cap's current window, and refuses if the total reaches the cap. Where several caps cover a request, on one scope or on several, the tightest decides. On Free, the organization's month total decides first whether the caps hold at all (On Free).
A refused request is never forwarded. There is no provider call, no token and no cost for it.
What comes back
Served
Your provider's response, with the kind and the window of the tightest armed cap the request was bound by:
x-recovea-request-id: req_0192f3a4b5c6d7e8f9a0b1c2d3e4f5a6
x-recovea-budget-scope: organization
x-recovea-budget-window: monthx-recovea-budget-scope is organization, key or customer, and run where
a Breaker run's own budget is the tightest. Under an armed cap on
your organization or a key, no figure rides a served response: what is left on
such a cap is in the platform, not on the wire. Under an armed cap on one of
your customers, the pair names that customer's cap and its figures ride with
it: x-recovea-budget-remaining, x-recovea-budget-cap and, on a cap that
resets, x-recovea-budget-resets-at, so your code can see what that customer
has left on every response
(the wire).
With no armed cap over the request, no budget header is sent.
Refused at the cap
HTTP/1.1 402 Payment Required
{
"error": {
"message": "Monthly budget reached. Traffic resumes at reset, or raise the cap.",
"type": "insufficient_quota",
"param": null,
"code": "budget_exceeded"
}
}The refusal carries the same two headers, naming the cap that refused:
x-recovea-budget-scope: organization and x-recovea-budget-window: month.
The status, error.type, error.code, the null param and the field order
message, type, param, code are the contract, and they are the same for every
cap and every window. Branch on them. The message is prose: it names the window
that was reached, and it may be reworded.
| Window | The message on an organization cap |
|---|---|
day |
Daily budget reached. Traffic resumes at reset, or raise the cap. |
week |
Weekly budget reached. Traffic resumes at reset, or raise the cap. |
month |
Monthly budget reached. Traffic resumes at reset, or raise the cap. |
year |
Yearly budget reached. Traffic resumes at reset, or raise the cap. |
lifetime |
Lifetime budget reached. This budget does not reset; raise the cap to continue. |
A cap on one API key says so after the window: "Monthly budget reached for this
API key. Traffic resumes at reset, or raise the cap.", and a cap on one of your
customers says "Monthly budget reached for this customer. Traffic resumes at
reset, or raise the cap.", with x-recovea-budget-resets-at beside the pair.
The sentence is built as the window's opening, the scope's words, then the
window's closing. No refusal
body names the cap, the key, the project or the run; the headers name the kind,
and x-recovea-request-id is the join to your own record. On Free, every one
of these sentences ends with a second one that names your organization's
ceiling (On Free).
Refused in pause mode
An armed cap refuses in one of two modes, chosen on the cap form under At the
cap: Block (402), the default, or Pause (429 with Retry-After). Pause
answers 429 with error.type rate_limit_error, the same error.code
budget_exceeded, and a Retry-After of the seconds until the window resets.
Branch on the code, not the status: nothing else separates a cap's 429 from a
throughput 429. A cap on one of your customers is block only: it stops, and
never pauses.
Refused because spend could not be confirmed
When an armed cap cannot confirm the spend it is counting, the request is
refused with 503, Retry-After: 1 and error.code budget_unverifiable,
never with the 402. The condition clears on its own.
Fail-open and fail-closed has the
body and the reason.
What a cap does not do
- It stops the next request, not the ones already running. The request that crosses the line was admitted and finishes, and so do requests running at the same moment. A scope's total can end above its cap by about the cost of the requests in flight when it was reached. If you need a hard stop at a number, set the cap below it by the cost of your largest request.
- It counts a floor for each request while it runs, and the floor can be more than the request costs. The floor added at admission counts the size of the request body in bytes divided by four, at the lowest rate the gateway's price list charges for any input token, cached or not, rounded down. Images, audio and files count every byte of their encoding, so a cap can refuse a request whose priced cost would have fit. When the response ends, the price the gateway gives it replaces the floor.
- It does not bound spend the gateway cannot price. A request the gateway cannot price, including one the provider refused or never answered, counts toward the cap while it runs, and when it ends its floor is released on the node that admitted it. That includes every request on a model the gateway's price list does not carry. In two cases a floor outlasts its request. When the gateway's nodes exchanged the counter while the request ran, the floor can stay on the counter until the cap's window ends, since an exchange only ever raises a counter. While a send from the gateway to its metering queue is slow, the floors of requests that have already ended stay on the node that admitted them, and a cap can refuse a new request on those floors. Once the send returns they are released, or replaced by the requests' prices. The gateway gives each send ten seconds: one that runs past that is cut off, what the queue had not taken is kept on the node and sent again, and the floors waiting behind it are released or replaced at once. When the queue is slow but answers, a floor waits on every send ahead of it, each held to ten seconds, and with many ended requests waiting that can run to minutes. A request whose client left after it was sent has its floor released too, though your provider may bill the call.
- It bounds routed traffic only. A provider key used directly never enters the gateway, and no cap reaches it. A provider's batch jobs pass through uncounted: the usage they run up is outside every cap and every figure.
On Free, a total per organization
Caps on your organization and on your keys work on every plan. A cap on one of your customers is Team and above; on Free your organization keeps the count of customers tagged this month and what they cost in total (Customer spending caps). On Free the caps protect up to the Free ceiling, and the ceiling is a total: it is counted against your organization's routed spend in the calendar month, across every cap and every key, never against each cap on its own. The figure is on the pricing page.
- Below the ceiling, every armed cap refuses at its line. A monthly cap set above this organization's figure is stored at that figure, and the platform says so when the cap is saved; once your organization's month reaches the figure every cap stops holding (observe-only for the rest of the month). A cap on another window holds as written while the month's total is under it.
- Every cap refusal names the ceiling. On Free, the sentence of a cap's
402, of its429in pause mode and of the503ends with a second sentence that names your organization's ceiling. The status,error.typeanderror.codedo not change. - At the ceiling, the caps turn observe-only for the rest of the month. Once the organization's spend in the month reaches the ceiling, the caps go on counting and refuse nothing until the month turns, at 00:00 UTC on the 1st. Metering and receipts carry on and the platform keeps showing the month; no cap blocks a request, and nothing is billed. Right after the month reaches the ceiling, a gateway node whose own count of the month is behind still refuses at a cap's line, until the platform tells it the ceiling was reached or the ledger's figure for the month arrives. Each gateway node keeps in its own memory that the month reached the ceiling, so the caps can hold again before the month turns: on a node that restarts after a later correction has brought the month's figure back under the ceiling, and on every node if this organization's ceiling is raised above the month's figure.
- A company domain shares one ceiling. The Free organizations founded on one company email domain since domain sharing began on 2026-10-09, at most five, share one ceiling in equal parts, so each is protected to its share and its refusals name that share; the Limits page shows this organization's share. An organization alone on its domain, on a public mail domain, or founded before domain sharing began has the whole ceiling, and so does one that comes back to Free from a paid plan when its domain already holds five Free organizations (One organization per email).
- A run's envelope still holds. A run's own budget, declared in
x-recovea-run-budget, is your line for that one run rather than a plan feature: at and above the ceiling it still refuses at its own line, while every cap on the organization's spend is observe-only (Breaker). - The kill switch has no ceiling. It refuses on every plan, Free above its ceiling included, as long as the gateway holds your organization's current keys and switches (The kill switch).
HTTP/1.1 402 Payment Required
{
"error": {
"message": "Monthly budget reached. Traffic resumes at reset, or raise the cap. On the Free plan, spending is protected to $2,500 a month in total, per organization.",
"type": "insufficient_quota",
"param": null,
"code": "budget_exceeded"
}
}The kill switch
The kill switch is a stop, not a cap: it has no amount and no window. It is on Settings → Organization → Limits, in its own section above the caps: one switch for the whole organization, which an Owner or an Admin may turn on and off, and one for each live key, which a Member may turn on and off too. While a switch is on, and the gateway holds your organization's current keys and switches, every request it covers is refused before your provider is called, on every plan, Free above its ceiling included. A gateway node that cannot read them knows no stop (Fail-open and fail-closed). Each change answers with a receipt, and the platform prints its id.
HTTP/1.1 402 Payment Required
{
"error": {
"message": "Spending is stopped for this API key: the kill switch is on. No request goes through until it is turned off.",
"type": "insufficient_quota",
"param": null,
"code": "kill_switch_on"
}
}On the whole organization, the sentence reads "Spending is stopped for this organization: the kill switch is on. No request goes through until it is turned off."
- Branch on
error.code. The status anderror.typeare a cap's, andkill_switch_onis what tells the two apart. Do not retry: the refusal carries noRetry-After, because nothing ends it but turning the switch off. - It comes before throttling and before every cap. A request it refuses is
not throttled and reads no cap, and the refusal carries neither
x-recovea-budget-scopenorx-recovea-budget-window. Like a cap's refusal, it is never forwarded and costs nothing. - It fails closed. Where a cap would answer
503because its spend cannot be confirmed, or where a throttling fault would let a request through, a request the kill switch covers is still refused with its402, as long as the gateway holds your organization's current keys and switches. - It is not a cap's hard stop. Enforce a hard stop arms a cap at its amount; the kill switch has no amount, and refuses everything it covers until it is turned off, as long as the gateway holds your organization's current keys and switches.
- A key's switch goes where the key goes. Revoking a key turns its switch off, since a revoked key routes nothing; rotating a stopped key gives the new key a switch that is already on.
Warnings and notices
Recovea does both: the warning on the way up, the refusal at the line. Threshold
alerts are Team and above; a refusal mails your owners and billing contacts on
any plan, Free included, once per budget each month.
Alert thresholds has the percentages. A cap on
one of your customers mails nobody: its warning is
x-recovea-budget-remaining on every response served under it, and its refusal
is the 402 your code sees
(No mail per customer).