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#

  1. 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.

  2. 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.

  3. 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.

  4. 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, lockpin also has limit, period, limit_usd and resets_at.

  • Every 402’s lockpin object has receipt: 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

402 budget_exceeded

The call’s worst case does not fit a budget or limit.

429 spend_rate_exceeded

The spend circuit breaker tripped for this task or key (below).

429 loop_stopped

You pressed Stop on a loop question (Loop checks and drift).

400 unknown_model

The model has no price, so no cap could hold.

400 unpriced_tool

The call uses a tool fee or tier Lockpin cannot price, on a task with a budget.

403 uncapped_call

Solo mode: a call Lockpin cannot meter, not listed in -allow-uncapped.

401 key_expired

Team mode: the Lockpin key has ended.

503 ledger_unavailable, keys_unavailable, reserve_failed

Lockpin’s own part failed (below).

500 handler_panic

A bug in Lockpin’s code; the server keeps running.

What the reply tells your agent#

Header

Meaning

X-Lockpin-Cost-Usd, X-Lockpin-Spent-Usd, X-Lockpin-Remaining-Usd

This call’s cost, the task’s spend and what is left (non-streaming replies).

X-Lockpin-Calls-Left

About how many more calls the budget pays for at the task’s recent rate.

X-Lockpin-Receipt

The call’s receipt as one line of JSON (Receipts, ledger and reports).

X-Lockpin-Task-Warning

The task has passed a warning step of its budget (75, then 95).

X-Lockpin-Output-Limit

Lockpin set or lowered the call’s output limit so the cap holds.

X-Lockpin-Degrade

What Lockpin did near the cap: fit, trim or cache5m.

X-Lockpin-Key-Remaining-Usd, X-Lockpin-Key-Resets, X-Lockpin-Key-Expires

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-max changes 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=false turns 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=false turns it off; X-Lockpin-Degrade: off turns 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 (stream_cut, or client_left when your agent hung up)

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 (no_usage)

The same way, without the margin.

Other APIs (OpenAI, Gemini, Converse) with no usage

The reservation.

A reply with no usage at all (for example background: true)

The full reservation. Missing data can only make the cap stricter.

Lockpin stopped mid-call (lockpin_stopped)

The reservation.

The provider returns 5xx, or the connection drops after sending

Estimated input cost, marked failed (-charge-failed=false makes failures free).

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-uncapped go without a cap.

  • An agent that skips Lockpin. A base URL set back to the provider bypasses it. lockpin doctor checks 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-After and X-Lockpin-Breaker-Resets. Other tasks and keys are not affected. -breaker-by task|key|both chooses what is watched.

  • A breaker alert 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.json next 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-After is 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 in X-Lockpin-Fallback-Skipped. A served fallback is named in X-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

-on-fault block (default)

-on-fault allow

The ledger file cannot be written

503 ledger_unavailable with Retry-After, until a write works

Calls go on, capped from memory, marked X-Lockpin-Degraded: ledger

The ledger file cannot be read at start

Lockpin does not start

The same

The keys file is unreadable when reloaded (team mode)

503 keys_unavailable

The old keys keep serving, marked X-Lockpin-Degraded: keys

Reserving fails for a reason other than the budget

503 reserve_failed

Forwarded uncapped, marked X-Lockpin-Uncapped: reserve_failed

A panic in Lockpin’s code

500 handler_panic; the server keeps running

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_tokens also 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.

Next steps#