Portal
Documentation: all sections

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:

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:

  1. The key is frozen. Further requests return 402 with type: "key_frozen" until it is reset.
  2. A circuit_breaker_tripped webhook 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.