AgentFinOps
Payment rails move money. They do not watch what an agent spends it on, cap a runaway loop before it drains a balance, or hand you a signed account of where the money went. That is the metering gap, and it is where a gateway that sits in the request path can enforce what a dashboard, reading events after the fact, cannot. This page covers the two controls that close it: the circuit breaker and the signed spend statement.
The circuit breaker
A spend cap refuses individual requests once a key is over its limit, then keeps refusing as each new request arrives. A circuit breaker does something stronger: when rolling-window spend would cross the threshold, it freezes the key. The key stops working entirely until a human (or the orchestrator) explicitly resets it. The distinction matters for an unattended agent stuck in a loop: a cap lets it keep hammering the gateway at the boundary of its budget, while a breaker trips once and stays tripped, turning a slow leak into a single loud stop.
Set it at mint time on POST /v1/keys:
circuit_breaker_usd: the rolling-window spend that trips the breakercircuit_breaker_window_secs: the width of the rolling window, default 3600 (one hour)
curl https://api.buildsable.com/v1/keys \
-H "Authorization: Bearer $SABLE_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "overnight-agent",
"circuit_breaker_usd": 5,
"circuit_breaker_window_secs": 3600
}'When the breaker trips:
- The key is frozen. Further requests return
402withtype: "key_frozen"until it is reset. - A
circuit_breaker_trippedwebhook fires, so an operator or a supervising agent learns immediately, not at the next dashboard glance.
Reset is deliberate and explicit. POST /v1/keys/{id}/unfreeze (session-authed,
like minting) clears the freeze and the key resumes:
curl https://api.buildsable.com/v1/keys/$KEY_ID/unfreeze \
-H "Authorization: Bearer $SABLE_SESSION_TOKEN"Pair the two controls: a spend_window cap for the total budget you will tolerate
over a day or a month, and a much tighter circuit_breaker_usd over an hour to
catch a loop long before it reaches that cap. The cap is the ceiling; the breaker
is the smoke alarm.
Signed spend statements
A statement is a signed, verifiable roll-up of what a period actually cost.
POST /v1/usage/statement (session-authed) accepts an optional since and
until (ISO-8601; omit for the account's lifetime) and returns a statement you
can hand to anyone, verifiable without trusting Sable.
curl https://api.buildsable.com/v1/usage/statement \
-H "Authorization: Bearer $SABLE_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"since": "2026-08-01T00:00:00Z", "until": "2026-09-01T00:00:00Z"}'{
"statement": "eyJ2Ijoi…",
"signature": "0x4f8c…",
"signer": "0xA1b2…9F",
"payload": {
"account_id": "acct_…",
"since": "2026-08-01T00:00:00Z",
"until": "2026-09-01T00:00:00Z",
"total_cost_micro_usd": 1284500,
"by_kind": {
"chat": 910300,
"embedding": 24200,
"sandbox": 350000
},
"by_model": {
"sable": 720100,
"sable-llama-3.3-70b": 190200,
"sable-embed-3-small": 24200
},
"issued_at": "2026-09-01T00:00:03Z"
},
"verify": "POST /v1/receipts/verify { receipt: statement, signature }"
}
payload is the decoded statement: the period's total metered cost, broken out
by_kind (chat, embedding, sandbox) and by_model. It is signed secp256k1 /
EIP-191 by the same deployment signer as every
receipt, so it verifies through the same public endpoint, no special-casing:
curl https://api.buildsable.com/v1/receipts/verify \
-H 'content-type: application/json' \
-d '{"receipt": "<statement>", "signature": "<signature>"}'The figures reconcile by construction: a statement sums the same per-request metered
costs that ride in each receipt and appear in
GET /v1/usage/events. It is the receipts, rolled up and signed,
not a separate accounting that could drift from them.
Honest scope
A statement proves what Sable metered and billed over a period, signed so it can't be quietly edited afterward. It is an account of spend on this gateway, not an audited financial statement and not a claim about anything that happened off Sable. As with receipts, it is metadata only: costs, counts, and model ids, never prompts or completions.
Export to your observability stack (OpenTelemetry)
Sable can emit one metadata-only span per metered request to any OTLP/HTTP traces endpoint (Langfuse, Grafana, Datadog, or a plain collector), so you see Sable inside the observability tool you already use. Set the endpoint on the deployment:
SABLE_OTLP_ENDPOINT=https://your-collector.example/v1/traces
SABLE_OTLP_HEADERS=authorization=Bearer xyz
Each span carries GenAI-convention attributes plus sable.* (model, provider,
token counts, latency, cost, privacy tier, node, status, and the receipt's
request id). It never carries prompt or completion content. Off unless the
endpoint is set.