Portal
Documentation: all sections

Cost & metering

Every inference is metered. Pricing is published per model, the exact cost of each call rides along in its signed receipt, and your account totals roll up in GET /v1/usage, all in metadata, none of it touching prompt or completion content.

Per-model pricing

GET /v1/models advertises two price fields per model, both in USD per 1M tokens:

curl https://api.buildsable.com/v1/models \
-H "Authorization: Bearer $SABLE_API_KEY"

# {
#   "data": [
#     {
#       "id": "sable-llama-3.3-70b",
#       "prompt_usd_per_mtok": 0.12,
#       "completion_usd_per_mtok": 0.30,
#       ...
#     }
#   ]
# }

Per-request cost

Each call's metered cost is reported as cost_micro_usd (millionths of a dollar) inside the signed receipt. A cost of 270 means $0.000270. Using micro-USD keeps every figure an exact integer, with no floating-point rounding between the gateway and your books. The cost is prompt_tokens × prompt_usd_per_mtok + completion_tokens × completion_usd_per_mtok (the per-Mtok price is also the per-token micro-USD rate), so at the prices above 1,000 prompt + 500 completion tokens cost 1000×0.12 + 500×0.30 = 270.

{
  "model": "sable-llama-3.3-70b",
  "prompt_tokens": 1000,
  "completion_tokens": 500,
  "total_tokens": 1500,
  "cost_micro_usd": 270,
  "logging": "metadata-only"
}

Because the cost lives in a signed receipt, it's tamper-evident: the same secp256k1 signature that proves the inference happened also proves what it cost. See Verifiable receipts.

Token counts come straight from the upstream's usage block. On the rare streamed response where an upstream omits it, Sable estimates the counts locally so the call still meters instead of recording zero. It's an approximation, and only ever a fallback when there's no upstream count to use.

Prompt-cache passthrough

When an upstream serves part of your prompt from its prompt cache, it reports the number of cached tokens (OpenAI as prompt_tokens_details.cached_tokens, Anthropic as cache_read_input_tokens). Sable bills those cached prompt tokens at a discounted rate, passing the cache saving through to you rather than charging every input token at full price.

You do not opt in or change anything: send the same repeated system prompt or context an agent loop reuses, and if the upstream caches it, your bill drops automatically. The receipt makes it explicit and verifiable:

"cost_micro_usd": 240,
"cached_tokens": 500,
"cache_savings_micro_usd": 30

cached_tokens is how many prompt tokens hit the cache, and cache_savings_micro_usd is what that saved you on this call versus full-rate input. Both are omitted when there was no cache hit, so an uncached request is billed and receipted exactly as before. This is metadata only, in keeping with the privacy contract: Sable reads the cached count the upstream reports and never caches or stores any prompt content itself.

Reserved credit while a request is in flight

Credit is checked before a request reaches an upstream, and at that moment the real cost isn't known yet. So the gateway reserves the worst case for the life of the request and releases it the moment the request settles. Spendable credit is your balance minus everything currently reserved.

A reservation is not a charge. Nothing moves in the ledger until the request settles, and what's then debited is the metered cost, usually much less than what was held. Reservations are released as each request finishes, whatever its outcome, and one left behind by a request that crashed is swept automatically. They never touch the ledger, but both balance endpoints report them: GET /v1/billing/balance and GET /v1/credit return held_micro_usd, spendable_micro_usd, and spendable_usd alongside the raw balance. Spendable is the balance minus credit reserved by in-flight holds, and it is the number that decides whether the next request is authorized.

This is what stops many simultaneous requests from all passing a credit check that only one of them can pay for: a balance that covers one call authorizes one call, not a hundred.

The consequence for a caller is that a 402 can now describe a temporary state rather than an empty balance:

{
  "error": {
    "message": "account has no spendable credit (balance $0.4200, $0.4200 reserved by requests already in flight). Top up with USDT, or retry when those finish.",
    "type": "insufficient_credit"
  },
  "x402Version": 1
}

When the message names credit reserved by requests already in flight, the balance is real and simply committed elsewhere; retrying once those requests finish succeeds without paying anything more. A plain out-of-credit 402 says so instead, and both carry the x402 accepts terms so an agent can settle and retry; see Paying with USDT.

Account totals

GET /v1/usage aggregates spend across your account. The response carries total_cost_micro_usd for the period, and each request entry includes its own cost_micro_usd, so you can reconcile line by line.

import requests

usage = requests.get(
  "https://api.buildsable.com/v1/usage",
  headers={"Authorization": f"Bearer {SABLE_SESSION_TOKEN}"},
).json()

dollars = usage["total_cost_micro_usd"] / 1_000_000
print(f"Spend this period: ${dollars:.6f}")

/v1/usage is session-gated. It uses the dashboard bearer session (sess_…), not an sk-sable_… key. To cap what a single key can spend, set a spend_limit_usd when you mint it; see API key controls.

The raw usage ledger

For reconciliation beyond the aggregate, GET /v1/usage/events (session-gated) returns the raw usage ledger, filterable by since, until, key_id, kind, and status. Results paginate with a keyset cursor (pass back the response's next_cursor to continue), and ?format=csv exports the same rows for a spreadsheet or your books. Like everything metered, the rows are metadata only: model, tokens, cost, latency, status, never content.