How the cap works#
This page explains how Lockpin decides each model call: what it reserves, when it refuses, what your agent sees, and what happens when something fails. It is for anyone who runs agents through Lockpin and wants to know exactly how far a budget holds.
The short version#
Before a call reaches the provider, Lockpin prices its worst case and holds that amount against every limit the call is charged to. If the worst case does not fit, the call is refused and never sent. If it fits, the call goes through unchanged, and the hold is replaced by the real cost when the reply reports its usage.
All of Lockpin’s logic is plain arithmetic. It makes no model calls of its own.
What happens on each call#
Pre-flight. Lockpin sizes the input from the request (3 bytes per token, where real text runs about 6) and doubles that. If the worst case still fits the budget, the call goes straight on. Only near the cap, or for images, mostly non-ASCII text and unknown history, does it ask the provider for the exact input size, where the provider offers a count. If counting fails, it falls back to the estimate.
Reserve. Lockpin holds the worst case: every input token priced as uncached, plus the full output limit (
max_output_tokens, or 4096 if the request sets none). Reservations make parallel calls safe: thirty calls at once still cannot overshoot the budget.Refuse or forward. If the reservation does not fit, the call never reaches the provider and your agent gets HTTP 402 with
"code": "budget_exceeded". If someone is watching, Lockpin may first hold the call and ask (see Approvals at the cap). Otherwise the call goes through untouched, streaming included.Settle. The reservation is replaced by the real cost from the provider’s
usage, with cached tokens priced at the cached rate.
Reserved, refused and charged#
Term |
Meaning |
|---|---|
Reserved |
The worst-case cost held against a limit while a call runs. It counts against the budget until the call settles. |
In flight |
The sum of reservations for calls that have not finished yet. |
Charged |
The real cost of a call once it settles, from the provider’s usage report. |
Estimated |
A charge Lockpin had to estimate because usage never came. It is marked in the ledger and on the receipt. |
Refused |
A call Lockpin did not send because its worst case did not fit. It costs nothing. |
Because the worst case is reserved up front, a 402 can arrive while the budget still shows some money left: the next call might have cost more than that.
Which limits a call is checked against#
Each call is checked against every account it is charged to, in one step: the task’s budget, the machine’s daily ceiling (solo mode), and any key, team, user, tag and project limit that applies. If any of them would be passed, the call is refused. See Budgets and tasks for how each is set.
Only a task’s own budget can ask before refusing. Key, team, project and daily limits refuse at once.
The refusal your agent sees#
The 402 body is in the provider’s own error shape plus a lockpin object. On an OpenAI-style
route:
{"error":{"code":"budget_exceeded","message":"lockpin: task \"fix-login-bug\" would exceed its $0.0001 budget: spent $0.0000, in flight $0.0000, this call needs up to $0.0213","param":null,"type":"budget_exceeded"},
"lockpin":{"budget_usd":0.0001,"in_flight_usd":0,"needed_usd":0.021315,"spent_usd":0,"task":"fix-login-bug"}}
On the Anthropic route:
{"type":"error","error":{"type":"budget_exceeded","message":"lockpin: task \"fix-login-bug\" would exceed its $0.0001 budget: ..."},
"lockpin":{"budget_usd":0.0001,"in_flight_usd":0,"needed_usd":0.050092,"spent_usd":0,"task":"fix-login-bug"}}
The message always starts with
lockpin:, so the tool’s error output shows who stopped the call.When a team key’s limit is the one hit,
lockpinalso haslimit,period,limit_usdandresets_at.Every 402’s
lockpinobject hasreceipt: the refusal as a receipt ("decision":"refused").
Handle the stop in an agent loop by wrapping up with what you have:
import openai
try:
resp = client.responses.create(model="muse-spark-1.3", input=history)
except openai.APIStatusError as e:
if e.status_code == 402: # budget reached: wrap up with what you have
return partial_result
raise
Other statuses you may meet#
Status and code |
Why |
|---|---|
|
The call’s worst case does not fit a budget or limit. |
|
The spend circuit breaker tripped for this task or key (below). |
|
You pressed Stop on a loop question (Loop checks and drift). |
|
The model has no price, so no cap could hold. |
|
The call uses a tool fee or tier Lockpin cannot price, on a task with a budget. |
|
Solo mode: a call Lockpin cannot meter, not listed in |
|
Team mode: the Lockpin key has ended. |
|
Lockpin’s own part failed (below). |
|
A bug in Lockpin’s code; the server keeps running. |
What the reply tells your agent#
Header |
Meaning |
|---|---|
|
This call’s cost, the task’s spend and what is left (non-streaming replies). |
|
About how many more calls the budget pays for at the task’s recent rate. |
|
The call’s receipt as one line of JSON (Receipts, ledger and reports). |
|
The task has passed a warning step of its budget ( |
|
Lockpin set or lowered the call’s output limit so the cap holds. |
|
What Lockpin did near the cap: |
|
Team mode: the key’s limit left, when it resets, and when the key ends. |
Lockpin removes every X-Lockpin-* request header before the call goes to the provider.
Output limits and degrading near the cap#
A call with no output limit gets one. A capped call that sets none gets 32,000 tokens (or the model’s maximum, if lower), shrunk to what the budget left can pay for near the cap. Calls with no cap are left alone.
-fit-output-maxchanges the 32,000.Anthropic calls near the cap. Anthropic calls always set a limit, and Claude Code’s is large. Near the cap Lockpin lowers it to what the budget left can pay, down to 4,096 tokens, instead of refusing the call. The reply may then stop early.
-fit-output=falseturns this off.Degrade before refusing. Once a task has spent 80% of its budget (
-degrade-at), a call’s own output limit is lowered to what is left (down to 256 tokens), trimming of old tool results gets harder, and Anthropic calls stay on the 5-minute cache. The cap never rises: a degraded call is reserved and refused like any other. Key, team, user and tag limits never degrade.-degrade=falseturns it off;X-Lockpin-Degrade: offturns it off for one call, which is then refused instead.
Streaming#
Streamed calls are reserved at their worst case like any other, and go through untouched. Streamed
replies send their headers before the call is charged, so their receipts are in the meter and in
lockpin receipt, not in the reply headers. Lockpin adds stream_options.include_usage to Chat
Completions streams so they report usage.
When usage never arrives#
While a call runs, its reservation is always its worst case, so the cap holds whatever happens. What
changes is the charge afterwards when usage never comes. Every such charge is marked estimated,
with its reason in the ledger (estimate_reason), on the receipt, and as “(est.)” in the receipt’s
summary.
Situation |
What is charged |
|---|---|
An Anthropic stream cut short ( |
Input as the stream’s first event reported it. Output at least every relayed byte as a token, at most what the model could have written in the time plus a margin. |
An Anthropic stream that ended without usage ( |
The same way, without the margin. |
Other APIs (OpenAI, Gemini, Converse) with no usage |
The reservation. |
A reply with no |
The full reservation. Missing data can only make the cap stricter. |
Lockpin stopped mid-call ( |
The reservation. |
The provider returns 5xx, or the connection drops after sending |
Estimated input cost, marked failed ( |
The connection never opened, or the provider returns 4xx |
Nothing. |
How far past a budget a call can go#
By default, nothing passes a budget while prices are right: calls in flight are reserved too. In one measured run, 30 calls sent at once to a task whose budget paid for 10 at their worst case were held to the budget: 8 allowed, 22 refused with 402, overshoot $0.00.
Where Lockpin can still go past a limit:
A wrong price. A price that is too low, or a provider that bills something the price table does not know, can cost more than was reserved. The next call is then refused.
Lockpin’s own fault, with
-on-fault allow(below): the call is forwarded uncapped and marked.Calls Lockpin does not meter. Paths listed in
-allow-uncappedgo without a cap.An agent that skips Lockpin. A base URL set back to the provider bypasses it.
lockpin doctorchecks for that.
The spend circuit breaker#
A budget says how much; the breaker says how fast. It is off until you turn it on, in the meter’s Spend-rate breaker panel or with flags:
lockpin -breaker-usd 5 -breaker-window 60s -breaker-cooldown 5m
When a task (or, in team mode, a key) is charged more than -breaker-usd within -breaker-window,
it is tripped for -breaker-cooldown:
Its calls get HTTP 429 with
"code": "spend_rate_exceeded",Retry-AfterandX-Lockpin-Breaker-Resets. Other tasks and keys are not affected.-breaker-by task|key|bothchooses what is watched.A
breakeralert goes out once per trip.It counts settled charges, so a long stream’s cost lands when it ends. The reservation, not the breaker, bounds a call still running.
It resets by itself when the cooldown ends. The reset button in the meter ends it early. A pause is kept in memory, so a restart ends it.
A change made in the meter is saved as
lockpin-ledger.breaker.jsonnext to the ledger and used on the next start instead of the flags. Delete it to go back to the flags. Turning it off, changing it or resetting a pause is a loosening and shows in the meter’s Alerts as loosened.
Retries and fallback#
Both are off by default, because they change what the agent sees and how long it waits.
lockpin -retry-attempts 3 -fallbacks fallbacks.json
With
-retry-attempts 3, Lockpin retries a generation call itself on connection errors, 429, 408 and 500, 502, 503, 504 and 529, and a stream that ends before its first byte. Once your agent has seen a byte, a break is never replayed.No double charging. A retried call reserves its worst case once. Each failed attempt the provider may have billed is charged its estimated input, and the cap is checked again before every retry.
Retry-Afteris honoured up to-retry-after-max(30s). Otherwise the wait doubles from-retry-base(500ms) up to-retry-max(20s).A fallbacks file maps a model, or every model of a provider (
"openai/*"), to models to try in order. A fallback is re-checked at its own price before anything is sent, and skipped when it does not fit; the reply says why inX-Lockpin-Fallback-Skipped. A served fallback is named inX-Lockpin-Fallback.
{"fallbacks": {"openai/gpt-6-sol": ["openai/gpt-6-luna", "anthropic/claude-sonnet-5-5"],
"openai/*": ["deepseek/deepseek-v4-pro"]}}
The same file can list cheaper models to serve a call that does not fit its budget instead of refusing it. See Saving money.
If Lockpin’s own parts fail#
Lockpin guards money, so the default is to fail closed: a call that cannot be reserved and
recorded is refused, never sent unseen. -on-fault allow trades that guarantee for availability.
What fails |
|
|
|---|---|---|
The ledger file cannot be written |
|
Calls go on, capped from memory, marked |
The ledger file cannot be read at start |
Lockpin does not start |
The same |
The keys file is unreadable when reloaded (team mode) |
|
The old keys keep serving, marked |
Reserving fails for a reason other than the budget |
|
Forwarded uncapped, marked |
A panic in Lockpin’s code |
|
The same, and a call not yet sent is forwarded uncapped |
Drift snapshots or alert delivery fail |
The call goes on; shown as degraded |
The same |
An uncapped call is still recorded, and a fault alert goes out. GET /lockpin/healthz needs no
key and returns 503 only when calls are being refused, so a supervisor can poll it.
With the network cut#
The decision is local. Lockpin decides each call on the machine, from its own ledger file. It never waits on Lockpin’s servers, and it works with the network to them cut off.
Reporting to the hosted dashboard runs off the call path and never changes a decision.
Remote answers. If the relay for phone and chat answers cannot be reached, nobody counts as watching remotely, so calls are refused at once instead of held.
The provider. Without a route to the provider, calls fail as they would without Lockpin. A connection that never opened is not charged.
When Lockpin is not running at all#
Your agent’s calls then fail to connect. That is the safe default. If you make your client fall back to the provider directly, those calls have no cap, no meter and no record. Fall back only when Lockpin was not reached, never on a 402, 429 or 503 from Lockpin (those are decisions), and never after a read timeout (the call may be running). To keep Lockpin up, run it as the background service, which restarts it on failure.
Limits#
A call in flight when Lockpin crashes was reserved in memory only, so the provider may bill it with nothing in the ledger. A power cut can lose about the last second of ledger lines.
One Lockpin process holds the ledger. Several proxies do not share budgets.
Providers other than Meta, OpenAI and Anthropic are sized by estimate, which is pessimistic, so near the cap they block a little earlier than they need to.
xAI does not say whether
max_tokensalso limits reasoning, and OpenAI’s pro mode adds work that OpenAI does not say the output limit covers. A single such call could cost more than its reservation; the next call is then blocked.