usertrust
API Reference

Pricing

Usertoken costs per model across all four tiers, estimation functions, and the fallback rate.

All costs in the usertrust SDK are denominated in usertokens (UT).

1 UT = $0.0001 (one basis point of a dollar — a hundredth of a cent).

A budget of 100_000 UT equals $10.00. A budget of 1_000_000 UT equals $100.00.

Pricing Table

The SDK includes a 56-model pricing table in ledger/pricing.ts, current as of PRICING_TABLE_VERSION = "2026-11-02" (see PRICING_TABLE_VERSION). Rates are in usertokens per 1,000 LLM tokens, across four tiers: input (fresh prompt tokens), output, cache-read (cache-hit prompt tokens), and cache-write (cache-creation prompt tokens). A — means the provider publishes no rate for that tier — it is not free; see Cache tiers and the fallback invariant below.

Anthropic

ModelInputOutputCache ReadCache Write (5m)Cache Write (1h)
claude-fable-5-11005002.5125200
claude-fable-510050010125200
claude-mythos-5-1 (limited availability)1005002.5125200
claude-mythos-5 (limited availability)10050010125200
claude-opus-5-54020025080
claude-opus-550250562.5100
claude-opus-4-850250562.5100
claude-opus-4-750250562.5100
claude-opus-4-650250562.5100
claude-opus-4-550250562.5100
claude-mythos-preview (deprecated; Project Glasswing)250125025312.5500
claude-opus-4-1 (retired)15075015187.5300
claude-opus-4 (retired)15075015187.5300
claude-sonnet-5-52010012540
claude-sonnet-52010022540
claude-sonnet-4-630150337.560
claude-sonnet-4-530150337.560
claude-sonnet-4 (retired)30150337.560
claude-haiku-5-5 (prompt ≤ 100,000 tokens)150.11.252
claude-haiku-5-5 (prompt > 100,000 tokens)5250.56.2510
claude-haiku-4-51050112.520
claude-3-5-haiku (retired)8400.81016

Rates re-verified against Anthropic's published pricing page (platform.claude.com/docs/en/about-claude/pricing) on 2026-10-07; the 1-hour write column was re-read raw on 2026-10-08, and every row again on 2026-10-11.

Anthropic publishes cache-read at 0.1x base input and 5-minute cache-write at 1.25x, with three published exceptions on cache reads: 0.05x on claude-opus-5-5 and claude-sonnet-5-5, and 0.025x on claude-fable-5-1 and claude-mythos-5-1. claude-sonnet-5 is $2 / $10 per MTok: the introductory price became the standard price and the announced increase to $3 / $15 did not happen. The 1-hour cache write (2x base input, the last column) is its own tier: cacheWriteTokens stays the TOTAL written and a call that reports a 1-hour share (cache_creation.ephemeral_1h_input_tokens) prices that share at cacheWrite1hPer1k and the remainder at the 5-minute rate; see 1-hour cache writes.

A customRates entry is honoured only when the config's pricing field is "custom" (it is ignored under the default "recommended"), and it replaces the table row rather than merging with it. Restate cacheReadPer1k, cacheWritePer1k and cacheWrite1hPer1k in the entry: an omitted read or 5-minute write tier prices at inputPer1k, and an omitted 1-hour write rate at the dearer of the 5-minute write rate and 2× inputPer1k. The SDK prints a one-time warning when a custom entry drops cache tiers the table publishes for that model.

Per-request prompt tiers (Claude Haiku 5.5)

claude-haiku-5-5 is priced per request by prompt length. A request whose prompt is over 100,000 tokens pays the higher row for every category, output included; a prompt of exactly 100,000 tokens pays the lower row. The prompt length counts all of the request's input tokens: fresh input, cache reads and cache writes (a 1-hour write is part of the cache writes, so it is counted once). Output does not count toward it. Each request is priced on its own.

In the table this is ModelRates.longPrompt: the row's own rates are the lower tier, and longPrompt is { thresholdTokens: 100_000, rates }. costFromRates selects the tier from the counts it prices, so every estimate, settle and stream applies the same rule, and a receipt's pricing.appliedRates are the rates of the tier the call was settled at: the recompute below still reproduces its cost. Only built-in table rows carry a tier; customRates are flat, and a longPrompt on a custom row (for example one built by spreading a table row) is ignored.

The xAI rows use the same mechanism with thresholdTokens: 199_999: xAI applies its doubled long-context rates once a prompt reaches 200,000 tokens, so a prompt of 200,000 tokens or more pays the higher row and 199,999 or fewer pays the lower one (see xAI).

One settle is one request. A settle prices the counts it is given as a single request. A caller that settles several requests' usage together, such as the Claude Code plugin's transcript windows, is priced at the tier of their combined prompt. Before rounding, that is never below what the requests cost one by one, and never above the higher tier for all of them. The charge itself can be lower, because a settle is rounded up to a whole UT once, with a 1 UT floor: two requests that each cost 0.4 UT are charged 1 UT together and 2 UT apart. To be priced exactly, settle each request on its own.

Holds. trust() and createGovernor().authorize() size a hold from an estimated prompt, which cannot tell which side of the threshold the request will land on. They hold the higher tier; the settle posts the actual tier and the two-phase post releases the difference at once. A short Haiku 5.5 call therefore reserves about 5× what it settles at. The @usertrust/openshell gate sizes from an upper bound on the prompt, so it holds the lower tier whenever that bound is at or under 100,000 tokens.

1-hour cache writes

ModelRates.cacheWrite1hPer1k is the 1-hour write rate. Every Anthropic row carries it explicitly (the page's "1h cache writes" column). cacheWriteTokens is the TOTAL cache-write count, and the 1-hour tokens are a subset of it (never added to it): the 5-minute term is cacheWriteTokens - cacheWrite1hTokens. A 1-hour count above the total is clamped to it, and a 1-hour count with no write total prices nothing at the 1-hour rate; sent alone to a headless settle() it is not reported usage, so the settle meters at the pre-call estimate. A reported 1-hour share for a model whose built-in row publishes no 1-hour rate (Mistral, Gemini, OpenAI) is ignored: it has no such tier, so the whole write total prices at its single write rate. A custom row that omits cacheWrite1hPer1k resolves, in one place (effectiveCacheWrite1hRate), to the dearer of its 5-minute write rate and 2x inputPer1k: a silent row is never billed below 2x input.

Where the split comes from: the Anthropic usage.cache_creation breakdown, on both stream surfaces and the non-stream path; headless settle() takes cacheWrite1hTokens; usertrust-server accepts it on POST /v1/settle and lists cache-write-1h in /v1/health capabilities (an older server strips the key, so a client sends it only when the capability is listed). A payload that carries only the flat cache_creation_input_tokens total, and a host with no TTL split (the OpenClaw plugin's usage), prices at the 5-minute rate.

On the receipt, when the call wrote 1-hour cache: a ROOT field cacheWrite1h: { tokens, ratePer1k }, never inside usage or pricing.appliedRates (the v2 schema closes both). It is absent otherwise, so every other receipt is unchanged. An auditor's recompute splits the write term: (usage.cacheWriteTokens - cacheWrite1h.tokens) x appliedRates.cacheWritePer1k + cacheWrite1h.tokens x cacheWrite1h.ratePer1k.

OpenAI

ModelInputOutputCache ReadCache Write
gpt-4o2510012.5—
gpt-4o-2024-05-13 (snapshot priced above its alias)50150——
gpt-4o-mini1.560.75—
gpt-5.4251502.5—
o320805—
o4-mini11442.75—
gpt-5.6-sol (promotional price, "at least through November 21, 2026")40200450

The cached-input discount varies within the OpenAI family — gpt-5.4 reads at 0.1x, o3/ o4-mini at 0.25x — so there is no single family-level multiplier. OpenAI publishes no separate cache-write rate for the models above; writes bill at the standard input price, which is exactly what the fallback invariant reproduces by leaving cacheWritePer1k unset. gpt-5.6-sol is the exception and carries an explicit rate: from gpt-5.6 onward OpenAI bills cache writes at 1.25x the uncached input price, so leaving it unset would misprice them.

Moonshot AI

ModelInputOutputCache ReadCache Write
kimi-k3301503—

Moonshot's price list has a cache-hit and a cache-miss input column and no cache-creation column at all, so cacheWritePer1k is unset and writes price at the input rate through the fallback invariant. Kimi speaks the OpenAI wire protocol, so detectClientKind reports openai for a Kimi client — that is the client SHAPE, and it does not change which rates apply.

Google Gemini

ModelInputOutputCache ReadCache Write
gemini-2.5-flash3250.3—
gemini-2.5-pro12.51001.25—
gemini-3.1-pro-preview201202—

Context-cache reads are 0.1x base input. Cache creation bills as ordinary input plus an hourly storage charge this per-token model doesn't carry, so cacheWritePer1k is omitted rather than guessed — see Documented approximations.

xAI

Rates are xAI's published model pricing, retrieved 2026-11-02. xAI publishes a cached-input price and no cache-write price, so the cache-write column is — (writes price at the input rate). Every model below also has a 200,000-token long-context tier at exactly double each rate, cached input included: xAI applies it once the prompt reaches 200,000 tokens, so a prompt of 200,000 tokens or more pays the doubled rates for every category, and a prompt of 199,999 tokens or fewer pays the rates shown. The table cannot see a request's endpoint: xAI bills calls to its US regional endpoint at 1.1x these rates, which the estimate does not include.

ModelInputOutputCache readCache write
grok-4.720605—
grok-4.620605—
grok-4.520603—
grok-4.312.5252—
grok-4.20-0309-reasoning12.5252—
grok-4.20-0309-non-reasoning12.5252—
grok-4.20-multi-agent-030912.5252—
grok-build-0.110202—
grok-312.5252—
grok-4-070912.5252—
grok-4-1-fast-reasoning12.5252—
grok-4-1-fast-non-reasoning12.5252—
grok-4-fast-reasoning12.5252—
grok-4-fast-non-reasoning12.5252—
grok-code-fast-112.5252—

The last seven rows are xAI's retired slugs. xAI's "Grok Model Retirement on May 15, 2026" notice says requests to a deprecated slug are billed at grok-4.3 pricing, so each carries that row and its long-context tier. That includes grok-code-fast-1, which the notice routes to grok-build-0.1: routing is not billing, and where the notice's pricing paragraph and its routing note differ the higher price is metered. grok-3-mini and bare grok-4 are not on that notice and have no row.

Other Providers

No cache-tier pricing is currently published for these models; both cache columns fall back to the input rate.

ModelProviderInputOutput
mistral-largeMistral515
mistral-large-latest (alias)Mistral515
deepseek-chatDeepSeek2.84.2
deepseek-reasonerDeepSeek2.84.2
llama-4-maverickMeta (Bedrock)2.49.7
command-aCohere25100
sonar-proPerplexity30150
qwen-72bAlibaba2.93.9
nova-proAmazon832

CANONICAL SOURCE

Rates reflect the SDK's built-in table. Check packages/core/src/ledger/pricing.ts for the canonical source, or run usertrust pricing (--json for machine output) to print the table as resolved against your usertrust.config.json — input, output, cache-read and cache-write, the 1-hour write rate, and for claude-haiku-5-5 its higher per-request tier, with absent cache rates shown at inputPer1k, in $/MTok. It lists the models of the providers named in your config, or every table model when none are named.

Cache tiers and the fallback invariant

ModelRates carries optional fields beyond inputPer1k/outputPer1k:

interface ModelRates {
  inputPer1k: number;
  outputPer1k: number;
  cacheReadPer1k?: number | undefined;     // absent or undefined ⇒ prices at inputPer1k, never 0
  cacheWritePer1k?: number | undefined;    // absent or undefined ⇒ prices at inputPer1k, never 0
  cacheWrite1hPer1k?: number | undefined;  // 1-hour writes; absent ⇒ the dearer of cacheWritePer1k and 2 × inputPer1k
  longPrompt?: LongPromptTier | undefined; // per-request prompt tier (built-in rows only)
}

Absent means "no published rate," not "free." costFromRates resolves an absent (or non-finite/negative) cache rate to the model's inputPer1k — this is the one and only rate resolution site in the SDK. A model that omits cacheWritePer1k still bills cache-write tokens, at the same rate as fresh input.

This is deliberately fail-safe: understating a bill is the dangerous direction (a budget that looks fuller than the real invoice), so an unmodeled cache tier defaults to the conservative price, never to zero. Never treat an absent field as a discount.

Fallback Rate

Models not in the pricing table are priced at the highest rate the table publishes ("fail dearest"). An unknown id over-counts visibly instead of under-counting silently:

DirectionRate (per 1K tokens)
Input250 UT
Output1250 UT
Cache write312.5 UT (5m) / 500 UT (1h)
Cache read250 UT (the input rate; no cache discount is assumed)

These are the maxima over the table (today claude-mythos-preview, $25 / $125); a test recomputes them, so a dearer row cannot be added without raising the fallback. A fallback-priced record carries rateSource: "fallback" and unknown: true. An unrecognized model isn't known to be Anthropic-shaped, so no cache-read discount is attached. Set unknownModelPolicy: "deny" to refuse an unknown model instead of metering it. The fallback is the dearest rate the table KNOWS; it cannot bound a model the table does not list that costs more than every row in it (claude-mythos-preview was exactly that case before it got its own row). For a hard budget, unknownModelPolicy: "deny" is the guarantee.

Whether an unknown model is allowed to meter at this rate is governed by the unknownModelPolicy config field ("fallback" | "warn" | "deny", default "warn"). "fallback" meters silently; "warn" meters at the same rate and prints a once-per-process stderr warning per model; either way the receipt carries meter.rateSource: "fallback". "deny" throws PolicyDeniedError (unknown_model: <model> not in pricing table) before any PENDING hold is placed. Only cloud-scope calls can be unknown: a model on a local endpoint never reaches the fallback rate — it resolves through local.models (exact key, then longest trailing-* glob) and otherwise local.defaultRate (default { inputPer1k: 0, outputPer1k: 0 }, which the 1-UT floor settles at exactly 1 nominal usertoken).

Model Matching

getModelRates() resolves a model id in this order:

  1. an exact row;
  2. the id with exactly one trailing date suffix stripped — -YYYYMMDD (claude-haiku-4-5-20251001) or -YYYY-MM-DD (gpt-4o-2024-08-06) — then exact;
  3. the fallback rate, flagged rateSource: "fallback", unknown: true.

There is no prefix matching: claude-sonnet-5-5 is a different, separately priced model from claude-sonnet-5, and a longer id that merely begins with a table key used to be billed at that key's row while the receipt claimed an exact table match. The exact row is tried first because a dated snapshot can cost more than its alias: gpt-4o-2024-05-13 is $5 / $15 per MTok against gpt-4o's $2.50 / $10, so it has its own row and is never reduced to the alias. A dated snapshot with no row of its own is metered at its base row, which assumes it is priced like its alias.

Residue (priced at the fallback, flagged): Gemini …-preview-MM-DD ids, a seven-digit or non-numeric suffix, -fast, and Bedrock anthropic.…:0 / Vertex …@… ids. isModelPriced() is stricter on purpose: it checks for an exact key (or custom rate) only, so a dated snapshot meters at its base row but is not "priced" for a caller that must refuse what it cannot price exactly.

For an endpoint classified as cloud, an id written the way a gateway or cloud platform writes it (Bedrock, Vertex, OpenRouter, a -latest alias without a row of its own, or another case such as GPT-4o) is priced at the fallback rate, so its hold is larger too and a small budget can refuse the call. To refuse such a call instead, set unknownModelPolicy to "deny". To price one, set pricing to "custom" and add the exact id to customRates; a local endpoint ignores customRates and uses local.models and local.defaultRate. See LIMITATIONS.md section 16.

getModelRates("claude-fable-5")           // exact match → { 100, 500 }
getModelRates("claude-fable-5-20260301")  // dated snapshot → claude-fable-5 → { 100, 500 }
getModelRates("gpt-4o-2024-08-06")        // dated snapshot → gpt-4o → { 25, 100 }
getModelRates("gpt-4o-2024-05-13")        // exact row, priced above its alias → { 50, 150 }
getModelRates("claude-fable-5-1")         // its own row → { 100, 500, cache read 2.5 }
getModelRates("claude-opus-5-fast")       // unknown → fallback → { 250, 1250 }

Functions

getModelRates, isModelPriced, costFromRates, estimateCost, estimateInputTokens, PRICING_TABLE_VERSION, resolveAppliedRates, resolveRates and the ModelRates type are exported from the package root (import { ... } from "usertrust"). PRICING_TABLE, FALLBACK_RATE, effectiveCacheWriteRate and effectiveCacheWrite1hRate are not on the root — import them from the usertrust/pricing subpath.

getModelRates()

function getModelRates(model: string, customRates?: Record<string, ModelRates>): ModelRates

Look up the four-tier rates for a model. An exact key in the optional customRates map wins first; then an exact table row, then the id with one date suffix stripped, then the fallback rate (never a prefix match).

costFromRates()

function costFromRates(
  rates: ModelRates,
  inputTokens: number,
  outputTokens: number,
  cacheReadTokens?: number,  // default 0
  cacheWriteTokens?: number, // default 0; the TOTAL written (5m + 1h)
  cacheWrite1hTokens?: number, // default 0; the 1-hour SUBSET of cacheWriteTokens
): number

Compute usertoken cost across all four tiers from an explicit ModelRates. The four counts are expected to be disjoint — cached tokens must already be separated out of inputTokens before this is called, or they are double-counted. Absent cache rates resolve to inputPer1k (see Cache tiers and the fallback invariant); the result is floored at 1 usertoken, same as estimateCost().

// 1,000 fresh input + 500 output + 2,000 cache-read tokens on claude-fable-5
costFromRates(getModelRates("claude-fable-5"), 1000, 500, 2000, 0)
// → Math.max(1, Math.ceil((1000 * 100)/1000 + (500 * 500)/1000 + (2000 * 10)/1000))
// → Math.max(1, Math.ceil(100 + 250 + 20))
// → 370 UT

Each tier is computed as (tokens × ratePer1k) / 1000 — multiply before dividing. This is the same order the receipt schema publishes as ceil(sum(counts × rates / 1000)), and it is load-bearing rather than stylistic: in IEEE-754 the divide-first form (tokens / 1000) × ratePer1k can land a ulp above an integer (560 cache-write tokens at 12.5/1k give 7.000000000000001), which Math.ceil then rounds to a whole extra usertoken. Reconciling against a receipt with the published formula reproduces the charged cost exactly, on every input.

estimateCost()

function estimateCost(
  model: string,
  inputTokens: number,
  outputTokens: number,
  customRates?: Record<string, ModelRates>,
): number

Two-tier convenience wrapper around costFromRates() for callers that don't have a cache split — cacheReadTokens/cacheWriteTokens default to 0. The result is always at least 1 (floor to prevent zero-amount transfers in TigerBeetle).

// 1,000 input tokens + 500 output tokens on claude-fable-5
estimateCost("claude-fable-5", 1000, 500)
// → Math.max(1, Math.ceil((1000 * 100)/1000 + (500 * 500)/1000))
// → Math.max(1, Math.ceil(100 + 250))
// → 350 UT

estimateInputTokens()

function estimateInputTokens(messages: unknown[]): number

Estimate the input token count from a messages array before the LLM call. Uses a heuristic of approximately 4 characters per token with a 1.5x safety margin so the PENDING hold exceeds actual cost in the majority of cases.

This function handles:

  • String content and multi-part content blocks
  • Nested arrays (tool result payloads)
  • Per-message overhead (role, structure)
  • Tool-call overhead

Returns at least 1. This estimate never models cache state — see Documented approximations.

ModelRates

interface ModelRates {
  inputPer1k: number;        // usertokens per 1,000 fresh input tokens
  outputPer1k: number;       // usertokens per 1,000 output tokens
  cacheReadPer1k?: number | undefined;     // usertokens per 1,000 cache-hit tokens; absent or undefined ⇒ inputPer1k
  cacheWritePer1k?: number | undefined;    // usertokens per 1,000 cache-creation tokens; absent or undefined ⇒ inputPer1k
  cacheWrite1hPer1k?: number | undefined;  // 1-hour cache writes; absent ⇒ max(cacheWritePer1k, 2 × inputPer1k)
  longPrompt?: LongPromptTier | undefined; // a per-request prompt tier (built-in rows only)
}

interface LongPromptTier {
  thresholdTokens: number;   // a prompt OVER this many tokens pays `rates` for every category
  rates: Omit<ModelRates, "longPrompt">;
}

PRICING_TABLE_VERSION

const PRICING_TABLE_VERSION: string  // e.g. "2026-11-02"

Date-stamped version of the built-in PRICING_TABLE, bumped whenever any entry's rates change. Recorded on every settled LLM receipt (receipt.pricing.tableVersion; governed non-LLM action receipts meter no tokens and omit the pricing block) alongside the resolved rates (receipt.pricing.appliedRates) so a metered cost can be reproduced exactly against the table version that priced it, independent of the SDK version currently installed.

Hold sizing and the cache-write premium

The PENDING hold reserves the input leg at the dearest write rate the call can produce, not inputPer1k alone. A cache write can price above plain input — Anthropic's 5-minute write is 1.25x, the 1-hour write 2x — so sizing the hold off inputPer1k alone understates the reserve for any call that turns out to write to cache. The hold covers the worst case this request can produce:

  • the governed trust() path reads the request: it reserves the 1-hour rate only when the request declares a 1-hour TTL (a cache_control block with ttl: "1h" anywhere in it), and otherwise holds at max(inputPer1k, effectiveCacheWriteRate(rates)) exactly as before;
  • the headless authorize() path runs before the call and cannot see the TTLs, so it reserves the dearest write rate (the 1-hour one) for the input leg and for any stated estimatedCacheWriteTokens. Only a row that publishes a 1-hour rate reserves it, so a model with no 1-hour tier holds as before; operator-owned rates (customRates, local rates, the fallback) with no cacheWrite1hPer1k reserve the derived 2x input, as their 1-hour tokens are metered at that rate.

Consequence: holds on cache-writing workloads run roughly 25% fatter than an input-only hold would size them, and headless holds on Anthropic models run about 60% fatter on the input leg (2x input against 1.25x), so a 402 near the budget comes sooner. The trust() path pays the extra only for a request that declares a 1-hour TTL. Warm (cache-hit-heavy) calls settle well below the hold and release the difference — this is conservative, not wasted budget. A caller supplying an exact estimatedInputTokens for a call that turns out cache-cold can still see the settle capped at the hold (settlement_shortfall, see AGENTS.md's Money invariants) if the actual write premium exceeds even the widened reserve.

Documented approximations

Reconciliation against a provider's own invoice is approximate, not exact — a receipt's cost exactly reproduces usertrust's own configured metered cost (counts × appliedRates, per-call ceil, 1-UT floor), but not necessarily the provider's bill line-for-line, because:

1-hour cache writes are priced at the 1-hour rate only when the provider reports the 5m/1h split (a flat write total, or a host with no TTL split, bills at the 5-minute rate); long-context, service-tier, regional, modality, and cache-STORAGE charges (Gemini hourly storage, prompt-size-dependent rates; gpt-5.6-sol's >272k long-context uplift, 2× in / 1.5× out) not modeled — fixed per-model rates by design, except claude-haiku-5-5 and the xAI rows, which are priced per request by prompt length (see "Per-request prompt tiers" at /docs/api/pricing); fast mode is not priced (Opus 5.5 fast is $8 / $40, Opus 5 and Opus 4.8 fast $10 / $50; speed is a usage field, not a model id); per-call ceil + 1-UT floor differs from provider-side aggregation. Estimates never model cache state.

Cost Estimation Flow

During the two-phase spend lifecycle, costs are estimated twice:

  1. Before the call (PENDING): estimateInputTokens() produces a conservative input estimate. Combined with the output cap the request names for the output estimate — trust() reads max_tokens on Anthropic; max_output_tokens on OpenAI Responses; max_completion_tokens, else max_tokens, times n on OpenAI chat; config.maxOutputTokens times candidateCount on Google (with no Google cap, 4096 and never less than the thinking budget); any other call with no cap holds 4096; the headless controller uses maxOutputTokens ?? 4096 — and the cache-write premium guard above, the SDK calculates the hold amount. The 1.5x safety margin (plus the write-premium guard) means the hold usually exceeds actual cost.

  2. After the call (POST): Actual token counts from the provider response (or accumulated from stream chunks), split into all four tiers, produce the real cost via costFromRates(). The difference between the hold and the actual cost is released back to the available budget.