GitHubBook a demoStart routing
Browse docs

Cap spend per virtual key

Give a tenant or virtual key a hard monthly USD budget that fails safely — mint the key, shadow the limit, preview the would-refuses, then promote to enforce.

Last updated

On this page

Put a hard monthly USD ceiling on a single tenant (or one virtual key) so that when it burns through its budget, further requests are refused — honestly, with the refusal recorded, and without ever silently blocking on a healthy path.

Prerequisites

  • Governance is enabled and mode: ledger is set, so budget_usd (a LedgerSettled limit) can fold the decision log to settle spend.
  • A decision log is configured (ledger.decision_log.path) — a hard-enforced budget_usd limit refuses to arm without one.
  • Your tenancy tree is declared under governance.tenants (or created via the management API); the node you cap must exist.

1. Mint a virtual key for the tenant

Mint a ttv_-prefixed key bound to the owner node you want to cap. The secret is printed once — only its SHA-256 is stored.

$ tokentriage keys mint \
    --owner org:acme/team:ml/user:dev1 \
    --label "dev1 laptop" \
    --expiry 720h

You should see (illustrative):

kid:     vk_7Q3f9m2x
expires: 2026-08-28T14:02:11Z
This secret is shown ONCE and cannot be recovered — copy it now:
ttv_9f3c…c1a0

The caller presents it as Authorization: Bearer ttv_….

2. Attach the budget in shadow first

Add a budget_usd limit attached to that tenant, in mode: shadow. Shadow runs a real evaluation and records a would_refuse on the ledger row, but the engine structurally cannot refuse in shadow — so you gather evidence with zero blast radius.

Attach the limit to the user node (or use attach: { level: key } to template one budget per key):

governance:
  enabled: true
  limits:
    - id: dev1_monthly_budget
      attach: { node: org:acme/team:ml/user:dev1 }
      type: budget_usd
      amount_usd: 200          # dollars-decimal; stored on the nanodollar grid
      period: month            # day | week | month
      posture:
        mode: shadow           # evaluated, records would_refuse, never refuses

Reload the proxy and let real traffic flow through the key. Every request that would have breached the cap is stamped would_refuse: [dev1_monthly_budget] on its decision-log row.

3. Read the would-refuses with govern preview

Dry-run the proposed limit set over your logged traffic. Preview folds the ledger through the same B.8 accounting the live proxy uses, so the budget_usd numbers are exact and hand-count-verifiable.

$ tokentriage govern preview -c tokentriage.yaml

You should see (illustrative):

governance preview — governance_dry_run
  shadow mode (mode: shadow) is the primary pre-enablement instrument; this is the labeled dry-run over ledger history + live counters (§7.5)
  ledger: ~/.tokentriage/decisions.jsonl
  rows replayed: 4128

EXACT — LedgerSettled recomputation (hand-count-verifiable, exact):
  [budget_usd] dev1_monthly_budget @ org:acme/team:ml/user:dev1  burn $214.930000 / limit $200.000000  (frac 1.0747, rows 4128, unpriced_excluded 0)  ⟵ WOULD REFUSE NOW

APPROXIMATE — rate-class (label: rate_windows_approximated):
  (none)

WOULD BE REFUSED RIGHT NOW (exact over the recorded ledger; the LIVE gate may lag
by its disclosed overrun bound — in-flight/streaming rows not yet folded):
  [budget_usd] dev1_monthly_budget @ org:acme/team:ml/user:dev1  burn $214.930000 / limit $200.000000

The ⟵ WOULD REFUSE NOW marker and the WOULD BE REFUSED RIGHT NOW block tell you exactly which tenants the cap would bite today. If the numbers look right, promote it. (Add --json for the machine-readable frozen shape, or --log <path> to preview a specific log.)

4. Promote to enforce — soft, then hard

Climb the ladder one rung at a time. First enforce: soft (mode: on) — it fires the alert thresholds but still never refuses, so you confirm the alerting wiring on live traffic:

      posture:
        mode: "on"
        enforce: soft                 # alert-only; fires thresholds, never refuses
        thresholds: [0.5, 0.8, 0.95]

Once you trust the alerts, flip to enforce: hard with fail: open:

      posture:
        mode: "on"
        enforce: hard                 # refuses on breach
        fail: open                    # prefer availability; disclose a suspended accountant
        thresholds: [0.5, 0.8, 0.95]

Reload. A breaching request now gets a 429 with error.refused_by.limit_type: budget_usd and unit: usd, and the same refusal is mirrored onto exactly one decision-log row (row == wire).

You should see (illustrative), on a breaching request:

HTTP/1.1 429 Too Many Requests
x-tt-refused-by: dev1_monthly_budget
x-tt-refusal-type: budget_exceeded

Why this is safe

Because you shadowed before enforcing and previewed the exact ledger-settled numbers, you never enforce blind: the cap you switch on is the cap you already watched decide.