usertrust
API Reference

Types

Core TypeScript types exported by usertrust.

All types on this page except StreamUsage (internal to streaming.ts) are exported from the usertrust package entry point. The SDK uses TypeScript 5.9 strict mode with noUncheckedIndexedAccess and exactOptionalPropertyTypes.

Client Types

TrustOpts

Options passed to trust(). See trust() for full documentation.

interface TrustOpts {
  configPath?: string;
  /** @deprecated Not implemented. Setting this throws. */
  proxy?: string;
  /** @deprecated Proxy API key. Unused because proxy throws. */
  key?: string;
  budget?: number;
  /** Overrides the config file's tier; policy rules that match on `tier` are evaluated at it. */
  tier?: string;
  /** PII enforcement override: overrides pii in the config file. */
  pii?: TrustConfig["pii"] | undefined;
  /** Ledger identity for cost-center envelopes. Overrides config.parentUserId; validated by the same rule. */
  parentUserId?: string | undefined;
  /** Operator-declared policy scopes that scopePatterns rules match. Overrides config.scope. */
  scope?: string[] | undefined;
  dryRun?: boolean;
  vaultBase?: string;
  /** Endpoint classification override; wins over config.endpoints matchers and loopback autodetect. */
  endpoint?: Partial<EndpointInfo> | undefined;
  /** SDK methods to let through ungoverned, by the path the refusal names. */
  allowUngoverned?: string[] | undefined;
}

TrustedClient<T>

The governed client returned by trust(). The original client's shape with its governed methods (create, stream, parse, generateContent) rewritten to return receipts, plus destroy() and governAction(). GovernedShape is not exported; it is the mapped type that rewrites only the provider surface trust() actually intercepts.

type TrustedClient<T> = GovernedShape<T> & {
  destroy(): Promise<void>;
  governAction<R>(
    action: ActionDescriptor,
    execute: () => Promise<R>,
  ): Promise<GovernedActionResult<R>>;
};

TrustedResponse<T>

The return type of every governed create() / generateContent() call. The Anthropic messages.stream() and messages.parse() helpers are governed too but return the SDK object itself with a receipt hung off it: a Promise<TrustReceipt> for stream(), a settled TrustReceipt for parse().

interface TrustedResponse<T> {
  response: T;
  receipt: TrustReceipt;
}

TrustReceipt

Governance metadata returned with every LLM call. See trust() for field descriptions.

interface TrustReceipt {
  transferId: string;
  cost: number;
  /** Present only when the settle POST was capped at the reserved hold. */
  postedCost?: number;
  budgetRemaining: number;
  auditHash: string;
  chainPath: string;
  receiptUrl: string | null;
  settled: boolean;
  model: string;
  provider: string;
  timestamp: string;
  auditDegraded?: boolean;
  usageSource?: "provider" | "estimated";
  /** Four-tier disjoint token split. Present exactly when a complete report priced the call (see ReceiptUsage): always for "provider", and for "estimated" counts a headless settle priced from. */
  usage?: ReceiptUsage;
  /** The 1-hour share of usage.cacheWriteTokens and its rate. Present only when the call wrote 1h cache. */
  cacheWrite1h?: { tokens: number; ratePer1k: number };
  chunksDelivered?: number;
  actionKind?: ActionKind;
  endpoint?: { class: EndpointClass; runtime: LocalRuntime };
  meter?: {
    costBasis: CostBasis;
    rateSource: RateSource;
    computeMs?: number;
  };
  /** The rate half of the reconciliation surface. Frozen; every LLM settle emits it. */
  pricing?: {
    /** The four resolved per-1k rates the cost was computed with. */
    appliedRates: AppliedRates;
    /** Date-stamped PRICING_TABLE version the rates came from. */
    tableVersion: string;
  };
  budget?: { costCenter: string; remaining: number; fraction?: number };
}

cacheWrite1h is a root-level field for the same reason (and because usage and pricing.appliedRates are closed in the v2 schema): when a call wrote 1-hour cache, the write term of the recompute splits into (usage.cacheWriteTokens - cacheWrite1h.tokens) x appliedRates.cacheWritePer1k + cacheWrite1h.tokens x cacheWrite1h.ratePer1k. It is absent otherwise, so other receipts are unchanged.

pricing is a root-level sibling of meter, not a member of it. That placement is deliberate: receipt.v1.schema.json declares meter with additionalProperties: false while leaving the receipt root open, so growing meter would make every v1 validator reject every receipt usertrust emits — and v1 is frozen. Adding at the root keeps v1 validators green and costs recomputability nothing.

pricing.appliedRates is frozen, and each record surface (the receipt, the llm_call chain event, a stream's pre-settle handle) gets its own copy. Mutating what you were handed cannot make the rates recorded in the audit chain diverge from the rates your cost was computed with.

ReceiptUsage

The four-tier disjoint token split a settle was metered from (spec D5). Present on TrustReceipt.usage and llm_call audit events exactly when a COMPLETE report priced the call: input and output both present and usable (an explicit 0 counts). That is always so for provider-reported usage, and for a headless settle's own complete "estimated" counts. It is absent, never zero-filled, when the cost is the metered pre-call estimate (no counts, or counts refused as implausible), and for an INCOMPLETE report: an input or output absent, non-finite, negative or non-numeric, or cache tiers alone. An incomplete report costs max(the metered estimate, its partial counts) and publishes no usage, even when its counts set the cost; report an explicit 0 for a count that is genuinely zero. In trust(), a provider count that is present but not a count (a string, NaN, a negative) is refused instead: the whole report is recorded as implausible and costs the estimate exactly.

interface ReceiptUsage {
  inputTokens: number;      // fresh (non-cached) prompt tokens
  outputTokens: number;     // completion tokens, incl. provider-billed thinking
  cacheReadTokens: number;  // cache-hit prompt tokens
  cacheWriteTokens: number; // cache-creation prompt tokens
}

AppliedRates

The four RESOLVED per-1k rates a settle was metered with — i.e. after the cache-rate fallback invariant, so an absent cache tier appears here as inputPer1k, never as undefined or 0. Together with ReceiptUsage this is the whole reconciliation surface: ceil(sum(counts × rates / 1000)), floored at 1, reproduces TrustReceipt.cost exactly, from the record alone.

interface AppliedRates {
  inputPer1k: number;
  outputPer1k: number;
  cacheReadPer1k: number;
  cacheWritePer1k: number;
}

TrustReceipt's other inline field types: ActionKind is "llm_call" | "tool_use" | "file_access" | "shell_command" | "api_request"; EndpointClass is "local" | "cloud"; LocalRuntime is "ollama" | "vllm" | "lmstudio" | "openai-compat" | "unknown"; CostBasis is "usd-proxy" | "nominal"; RateSource is "table" | "custom" | "local-model" | "local-default" | "fallback".

LLMClientKind

The three supported provider types, detected via duck typing.

type LLMClientKind = "anthropic" | "openai" | "google";

Config Types

TrustConfig

The full configuration schema. See Configuration for field-level documentation.

type TrustConfig = {
  budget: number;
  tier: "free" | "mini" | "pro" | "mega" | "ultra";
  /** @deprecated Remnant of removed remote-proxy mode. Does not connect. */
  proxy?: string;
  /** @deprecated Proxy API key. Unused. */
  key?: string;
  /** Ledger identity for cost-center envelopes; validated by parentUserIdRefusal() at parse time. */
  parentUserId?: string;
  /** Operator-declared policy scopes: at most 32, each matching /^[a-zA-Z0-9._@:+/-]{1,128}$/. */
  scope?: string[];
  policies: string;
  pii: "redact" | "warn" | "block" | "off";
  injection: "block" | "warn" | "off";
  board: {
    enabled: boolean;
    vetoThreshold: "low" | "medium" | "high" | "critical";
  };
  circuitBreaker: {
    failureThreshold: number;
    resetTimeout: number;
  };
  patterns: {
    enabled: boolean;
    feedProxy: boolean;
  };
  audit: {
    rotation: "daily" | "weekly" | "none";
    indexLimit: number;
    /** When true a failed critical audit write denies the call instead of settling an unaudited spend. Default false. */
    failClosed: boolean;
  };
  tigerbeetle: {
    addresses: string[];
    clusterId: number;
  };
  providers: { name: string; models: string[] }[];
  pricing: "recommended" | "custom";
  /** Per-model per-1k rates. The two cache tiers are optional; an absent tier prices at inputPer1k. */
  customRates?: Record<string, ModelRates>;
  /** Endpoint matchers for classifyEndpoint(); first match wins. */
  endpoints: { match: string; class: EndpointClass; runtime: LocalRuntime }[];
  local: {
    autoDetectLoopback: boolean;
    defaultRate: ModelRates;
    rateClass: "nominal" | "amortized-usd";
    models: Record<string, ModelRates>;
    injectUsageOptions: boolean;
  };
  cloud: {
    injectUsageOptions: boolean;
  };
  unknownModelPolicy: "fallback" | "warn" | "deny";
  supplyChain: {
    enabled: boolean;
    trustedPublishers: string[];
    publisherKeys: Record<string, string[]>;
    allowedPermissions: SkillPermission[];
    requireSignature: boolean;
  };
  vault: {
    enabled: boolean;
    masterKeyEnv: string;
    auditAccess: boolean;
    defaultScope: { agents: string[]; actions: ActionKind[]; expiresAt: string | null };
  };
  anomaly: {
    enabled: boolean;
    tokenRate: {
      thresholdTokPerSec: number;
      localThresholdTokPerSec: number;
      perModel: Record<string, number>;
      windowMs: number;
      consecutiveWindows: number;
    };
    spendVelocity: {
      thresholdDollarsPerMin: number;
      localThresholdUsertokensPerMin: number;
      windowMs: number;
    };
    injectionCascade: { eventCount: number; windowMs: number };
    cooldownMs: number;
  };
};

Policy Types

PolicyRule

A single policy rule evaluated by the gate engine.

interface PolicyRule {
  name: string;
  effect: PolicyEffect;
  enforcement: PolicyEnforcement;
  severity?: PolicySeverity;
  conditions: FieldCondition[];
}

The rule the gate actually evaluates (and the policy YAML loader accepts) extends this with optional id, description, priority (lower wins, default 100), enabled (default true), scopePatterns (minimatch globs matched against the host's scope), and timeWindows. That GateRule type is internal to policy/gate.ts and is not exported from the entry point.

FieldCondition

A single condition within a policy rule. Uses dot-notation field resolution and one of 12 operators.

interface FieldCondition {
  field: string;
  operator: FieldOperator;
  value?: unknown;
}

FieldOperator

The 12 field operators supported by the policy gate.

type FieldOperator =
  | "exists"
  | "not_exists"
  | "eq"
  | "neq"
  | "gt"
  | "gte"
  | "lt"
  | "lte"
  | "in"
  | "not_in"
  | "contains"
  | "regex";
OperatorDescriptionExample Value
existsField resolves to a value other than undefined or null(none)
not_existsField is absent, undefined, or null(none)
eqStrict equality"gpt-4o"
neqNot equal"test"
gtGreater than (numeric)1000
gteGreater than or equal500
ltLess than (numeric)100
lteLess than or equal50
inValue is in the given array["gpt-4o", "gpt-4o-mini"]
not_inValue is not in the given array["deprecated-model"]
containsString contains substring"password"
regexMatches regular expression"^claude-.*"

Every comparison operator (everything except exists / not_exists) is unanswerable when the field does not resolve. An explicit null is a value the document carries, so it is answerable. Beyond that, what makes a condition unanswerable depends on the operator: gt, gte, lt and lte need a number on both sides, contains and regex need a string on both sides, and in and not_in need an array as the rule value. eq, neq, in and not_in compare the resolved value with strict equality, so a value of another type (the string "5" against 5) is a determinate comparison, not an unanswerable condition: false for eq and in, true for neq and not_in. On a hard rule an unanswerable condition counts as satisfied, so a hard gt rule on an absent field fires; on a soft rule it counts as unmet. An unknown operator is unanswerable too.

PolicyEffect

type PolicyEffect = "deny" | "warn";
  • deny -- Labels the rule as a denial. Whether a matching rule blocks is decided by enforcement, not by effect: the gate treats deny and warn identically, so a deny rule with enforcement: "soft" never throws.
  • warn -- Labels the rule as a warning. A warn rule with enforcement: "hard" still blocks the call with a PolicyDeniedError. Nothing is attached to the receipt: TrustReceipt has no warnings field, and trust() acts on the gate's decision, its reasons and its hardViolations.

PolicyEnforcement

type PolicyEnforcement = "hard" | "soft";
  • hard -- A matching rule is a hard violation regardless of its effect: the gate's decision becomes deny and trust() throws PolicyDeniedError before any hold is placed. A condition that cannot be evaluated (absent or mistyped field) counts as satisfied, so hard rules fail closed.
  • soft -- A matching rule is recorded in the gate's result (hasWarnings, softViolations, a [WARN]-prefixed reason) and never blocks. trust() does not read hasWarnings or softViolations, so a soft rule does not change whether the call proceeds and adds nothing to its receipt; its [WARN] reason shows up only inside the reason of a denial that a hard rule caused.

PolicySeverity

type PolicySeverity = "critical" | "high" | "medium" | "low" | "info";

Severity level for policy rules.


Audit Types

AuditEvent

A single entry in the SHA-256 hash-chained audit log.

interface AuditEvent {
  id: string;                         // Unique event ID
  timestamp: string;                  // ISO 8601 timestamp
  previousHash: string;               // Hash of the previous event (or GENESIS_HASH)
  hash: string;                       // SHA-256 hash of this event
  kind: string;                       // Event type (e.g., "llm_call", "settlement_ambiguous", "policy_denied")
  actor: string;                      // Actor identifier
  data: Record<string, unknown>;      // Event-specific payload
}

Each event's hash covers the previousHash, forming an append-only chain. The first event chains from GENESIS_HASH (64 zero characters). Editing an event makes verification fail at that event; hiding the edit means recomputing every later hash.


Streaming Types

GovernedStream<T>

An async iterable that wraps the native LLM stream, adding a receipt promise for the settled receipt. It resolves when the stream completes, or when the consumer stops early (the usage consumed so far settles). When the stream fails mid-way and its iteration throws (a cut connection, or a provider error event the SDK throws), it rejects with that same error, and the hold is voided rather than charged. An OpenAI Responses error event is not thrown: the SDK yields it as a chunk, and the stream settles, and is charged, like a completed one. Under audit.failClosed it can also reject with AuditDegradedError after settlement ran (the charge posted, or its POST failed and may still post), so the work may be billed; on the governed create streams that error, and only that one, also ends the iteration when the loop ends normally or with a break (any other settlement error, and a refusal while your loop body is throwing, reach receipt alone). A rejected receipt never becomes an unhandled rejection. The receipt returned beside response is provisional (settled: false). See Streaming.

interface GovernedStream<T> extends AsyncIterable<T> {
  receipt: Promise<TrustReceipt>;
}

Usage:

const { response } = await client.messages.create({
  model: "claude-fable-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Hello" }],
  stream: true,
});

try {
  for await (const chunk of response) {
    // Process chunks as usual
  }
  // The settled receipt, after the stream finishes
  const receipt = await response.receipt;
} catch (err) {
  // This catch also covers your own code in the loop, so it cannot say what the
  // error cost: see Streaming in the trust() docs for how to tell.
}

StreamUsage

Internal token accumulation from stream chunks — the four DISJOINT tiers of spec D2. inputTokens is fresh prompt tokens only: for providers whose prompt count is inclusive of the cache tiers (OpenAI, Gemini), the cached tokens are already subtracted back out by the extractors; for Anthropic, whose SDK counters are disjoint at the source, nothing is subtracted. Summing all four fields gives the billable total with nothing double-counted.

interface StreamUsage {
  inputTokens: number;
  outputTokens: number;
  cacheReadTokens: number;
  cacheWriteTokens: number;
  cacheWrite1hTokens?: number; // the 1-hour SUBSET of cacheWriteTokens (Anthropic); absent = 0
}