usage

package
v1.801.471 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package usage is what your org ran and what it cost, broken down per account.

It serves /v1/usage over one window grammar, and absorbed the account-usage board from apps/link, which owns links and nothing usage. It is NOT the only usage address — billing serves the wallet's own /v1/billing/usage{,/accounts} and /v1/finance/usage off the ledger — and the two answer different questions: this one composes the categorized roll-up, billing reports the raw drain on the wallet.

The surface:

  • POST /v1/usage record account-usage samples (the collector's data plane; write path over the hanzo.account_usage warehouse series, datastore.go).
  • GET /v1/usage/samples one provider account's own lane dash (the time series).
  • GET /v1/usage/summary the flagship own-scoped footprint roll-up (below).
  • GET /v1/usage/analytics{,/access} the entitlement-gated rich per-org read.

The summary answers "what am I running and what does it cost" by composing THREE complementary sources, each degrading independently to honest zeros (a source marker says which answered — a partial deploy never fabricates spend or usage):

  • Spend (the genuinely-missing categorized cost roll-up): the commerce ledger — usage/rollup (authoritative month-to-date consumed + prepaid wallet) plus the raw transaction ledger, rolled up server-side into spend-by-category over time. Every metered resource (GPU, machine-hours, LLM tokens, datastore footprint) debits this ONE ledger with a category tag, so it is already the unified cost source — this endpoint is the categorizing lens over it.
  • LLM usage totals: hanzo.cloud_usage, the per-org warehouse ledger (the same table /v1/analytics/* and the o11y board read). Totals only here — the per-model / timeseries detail stays at /v1/analytics/*.
  • The account board: the caller's OWN linked provider accounts (a Claude Max plan's window %, metered from the provider's own login) beside the org's Hanzo-routed usage, every row labelled by source/scope and NEVER summed. This is the account-usage global view, unified here from apps/link.

The console Usage view composes THIS with the existing org-scoped inventory endpoints (/v1/machines, /v1/gpus, /v1/agents, provisioning lists) for the per-kind counts — one screen, one categorized cost lens.

TENANT ISOLATION (the bar). The org is the VALIDATED IAM owner claim (principal.Org — the trusted X-Org-Id the identity middleware minted from the caller's verified bearer, HIP-0026; NEVER a client header) AND a validated principal is required (c.User() set only for a verified bearer). The commerce subject is pinned server-side to that org; every warehouse query binds org (and, for the account board, subject) POSITIONALLY. A caller can only ever read/write its OWN org — fail-closed: no principal → 401.

Its plugin does not declare OwnsHealth, so the host's generic GET /v1/usage/health liveness route stands — a distinct path that never shadows these. The manifest.Apps sequence is what binds /v1/usage/* ahead of the ai subsystem's /v1/* catch-all.

Index

Constants

View Source
const (
	Range1h  = "1h"
	Range24h = "24h"
	Range7d  = "7d"
	Range30d = "30d"
)

Ranges — the closed allowlist for a sample-dash read window. This is the FINE-GRAINED account-usage grammar (a live lane dash cares about the 1h/6h scale); the coarser cost/analytics grammar (aiobject.ResolveCloudUsageWindow: 24h/7d/30d/custom + a bucket interval) drives summary and analytics. Two lanes, two grammars, each complete for its read.

View Source
const (
	Window6h    = "6h"
	WindowDay   = "day"
	WindowWeek  = "week"
	WindowMonth = "month"
)

The CANONICAL window value: the ONE closed vocabulary that rate limits, quotas, and usage rollups all share, platform-wide. Lowercase, no case variants, no synonyms — a window is one of exactly these four values.

This declaration is the single source of the vocabulary. (hanzoai/commerce today carries an ad-hoc set — weekly/daily/monthly/hourly plus capitalized variants, and no sub-day window at all; a later commerce pass adopts THESE values. Do not copy the old strings back in.)

View Source
const (
	ConfidenceExact       = "exact"
	ConfidenceEstimated   = "estimated"
	ConfidencePercentOnly = "percentOnly"
	ConfidenceUnknown     = "unknown"
)

Confidence values — how real a sample's numbers are. These mirror @hanzo/usage's UsageDataConfidence EXACTLY (they are the meter's own wire values, already carried on Usage.Confidence), so a value crosses the whole system unmapped.

This is the ANTI-FABRICATION FLAG, and the reason it is a column: a Claude sample is `percentOnly`, so its token counters are 0 because they are UNKNOWN — not because the account consumed nothing. Without this value a reader cannot tell "0 tokens" from "no idea", and would render a fabricated zero. With it, the console renders "—".

View Source
const (
	SourceAccount = "account"
	SourceHanzo   = "hanzo"
)

Sources for the global view: which plane a usage row came from. They are NOT interchangeable and are never added together —

  • SourceAccount the provider's OWN plan consumption, metered from the user's own login. Its cost is what the PROVIDER says it charged (0 for a flat subscription); its percent is plan quota. NOT a Hanzo charge.
  • SourceHanzo hanzo.cloud_usage — Hanzo-routed inference. COST OF RECORD.
View Source
const (
	ScopeUser = "user" // the caller's OWN linked accounts (org+subject)
	ScopeOrg  = "org"  // the whole org's Hanzo-routed usage
)

Scopes for a global-view row: the tenancy the row's numbers cover. The two sources answer at different scopes and the row says which, so a reader never silently compares a user's plan usage against an org's whole spend.

View Source
const (
	KindSubscription = "subscription"
	KindAPIKey       = "apikey"
)

Account-kind vocabulary — how a metered account is credentialed. It mirrors the link registry's Kind (subscription | apikey) so a Sample and its Link agree, but each package validates its OWN inputs: the value is a fact about the account, not a shared place, so the two are decoupled and neither can force a change on the other.

Variables

View Source
var FreeAnalyticsAccess = AnalyticsAccess{Datastore: false, RetentionDays: 7, Export: false}

FreeAnalyticsAccess is the fail-closed floor: basic own-org usage only — no rich datastore surface, a 7-day query window, no export. It is applied whenever no plan can be resolved (unknown plan, or the catalog is unreachable), so a catalog outage degrades to exactly Free — never above.

Functions

func Mount

func Mount(app cloud.Router, deps cloud.Deps) error

Mount wires the usage surface onto app per HIP-0106 — one line over the generic subsystem entrypoint: build the state, register the routes.

Types

type Accounts

type Accounts struct {
	// Rows is the two row sets CONCATENATED, never summed — each row says which
	// side it came from. A percent is not money and a provider's own spend is not
	// a Hanzo charge, so adding them would produce a number that means nothing.
	Rows []TotalView `json:"rows"`
	// Account is the state of the caller's own linked-account side.
	Account SourceState `json:"account"`
	// Hanzo is the state of the org's Hanzo-routed side.
	Hanzo SourceState `json:"hanzo"`
}

Accounts is the account-usage board folded into the summary: the caller's OWN linked provider accounts (metered from each provider's own login — a Claude Max plan's window %) beside the org's Hanzo-routed usage. Every row is labelled by source/scope/confidence and the two are NEVER summed together — a plan's percent is not money, and a provider's own spend is not a Hanzo charge. Each side reports its own availability, so half a warehouse never turns the other half into zeros. This is the account-usage plane's global view (moved from clients/link) unified under the ONE /v1/usage/summary; the per-account time series is GET /v1/usage/samples.

type AnalyticsAccess

type AnalyticsAccess struct {
	Datastore     bool // analytics.datastore
	RetentionDays int  // analytics.retention_days
	Export        bool // analytics.export
}

AnalyticsAccess is the resolved analytics entitlement for one caller. Datastore grants the rich per-org analytics/datastore surface; RetentionDays caps how far back the tenant may query; Export allows data export.

func AnalyticsAccessFromEntitlements

func AnalyticsAccessFromEntitlements(ent map[string]any) AnalyticsAccess

AnalyticsAccessFromEntitlements maps a resolved `analytics.*` entitlement block onto AnalyticsAccess. Pure and total: any key absent from the block keeps the Free-floor value, so a partial catalog can only ever NARROW access (never widen past the floor). Wrong-typed values are ignored (kept at floor).

func ResolveAnalyticsAccess

func ResolveAnalyticsAccess(ctx context.Context, planID string) (AnalyticsAccess, error)

ResolveAnalyticsAccess resolves analytics access for a plan id from the @hanzo/plans catalog (the single source of truth). An empty id short-circuits to FreeAnalyticsAccess (the floor) WITHOUT a catalog round-trip, so an unauthenticated or plan-less caller fails closed and CI stays catalog-independent. On any resolution error it returns FreeAnalyticsAccess together with the error, so callers fail closed while logging the degradation.

type CategorySpend

type CategorySpend struct {
	// Category is the bucket the ledger's own tag mapped to. An untagged or
	// unrecognised line gets its own honest bucket rather than being folded away.
	Category string `json:"category"`
	// Cents is what the org spent in that bucket over the window, in US cents.
	Cents int64 `json:"cents"`
	// Count is how many ledger lines rolled up into it.
	Count int64 `json:"count"`
}

CategorySpend is one row of the spend-by-category breakdown: a friendly category bucket, the total cents spent in the window, and how many ledger lines rolled up into it. Categories are DERIVED from the commerce ledger's own tags — never fabricated — so an unknown tag surfaces as its own honest bucket.

type LLM

type LLM struct {
	// Available is false when the warehouse was not connected or a query blipped.
	// The totals below are then honest zeros, NOT measured ones.
	Available bool `json:"available"`
	// Requests is how many completions the org made in the window.
	Requests int64 `json:"requests"`
	// Tokens is the total tokens those completions consumed.
	Tokens int64 `json:"tokens"`
	// PromptTokens is the input half of that total.
	PromptTokens int64 `json:"promptTokens"`
	// CompletionTokens is the output half.
	CompletionTokens int64 `json:"completionTokens"`
	// CostCents is what they cost the org, in US cents. This IS a Hanzo charge.
	CostCents int64 `json:"costCents"`
	// Models is how many distinct models were used.
	Models int64 `json:"models"`
	// Source names the warehouse table the totals came from.
	Source string `json:"source"`
}

LLM is the org's LLM usage totals from the warehouse ledger. Available=false when the datastore is not connected (honest zeros). The detailed per-model / timeseries breakdown lives at /v1/analytics/*; this is the KPI-band total.

type ProviderBreakdown

type ProviderBreakdown struct {
	// Available is false when the warehouse could not be read, which means "no
	// answer" and NOT "no usage" — Items is then empty for a reason.
	Available bool `json:"available"`
	// Items is one row per provider, most tokens first.
	Items []ProviderRow `json:"items"`
	// Source names the warehouse table the rows came from.
	Source string `json:"source"`
}

ProviderBreakdown is the per-provider roll-up. Available=false is honest-empty (the datastore is not connected) — never fabricated, exactly like the summary's LLM block.

type ProviderRow

type ProviderRow struct {
	// Provider is the upstream the requests were routed to, e.g. anthropic.
	Provider string `json:"provider"`
	// Requests is how many completions the org made against that provider.
	Requests int64 `json:"requests"`
	// Tokens is the total tokens those completions consumed, prompt plus
	// completion.
	Tokens int64 `json:"tokens"`
	// CostCents is what they cost the org, in US cents.
	CostCents int64 `json:"costCents"`
}

ProviderRow is one provider's windowed totals. BYO/fee/account columns land with the hanzoai/ai metering-write branch; see buildAnalyticsBlock for the one-line projection extension.

type Sample

type Sample struct {
	Provider string // matches Link.Provider / @hanzo/usage providerRegistry id
	Account  string // the account label (an identifier, never a secret)
	Plan     string // the plan name from the provider identity; display only
	Kind     string // subscription | apikey (validKind) — mirrors Link.Kind
	Machine  string // the machine that OBSERVED this (an attribute, not a key)

	Lane          string    // the meter's lane id (five_hour|seven_day_opus|…) — the identity
	Window        string    // the canonical class (6h|day|week|month)
	WindowMinutes int32     // the duration the meter reported (300 for Claude's 5h)
	WindowStart   time.Time // WHICH instance of the window this measures — the dedup key
	ResetsAt      time.Time // when the window resets (RateWindow.resetsAt); zero = unknown

	// UsedPct is the lane's used percent, 0..100 — RateWindow.usedPercent. For a
	// subscription account this is THE quota signal and often the ONLY one.
	//
	// QUOTA GAP (deliberate, reported, not faked): there is NO absolute
	// used/limit token quota column, because nothing can populate one. The meter
	// reports a percent, not a limit (Claude: dataConfidence `percentOnly`). The
	// hanzoai/plans catalog expresses `ai.requests_per_min` / `ai.tokens_per_min`
	// — per-MINUTE rate limits, a different concept from a window quota — and in
	// any case it is HANZO's own plan catalog, which cannot know what Anthropic
	// grants a subscription plan. An always-zero quota_limit would read as "the
	// limit is zero"; absent is honest. It lands as an additive column when a
	// source for it exists.
	UsedPct    float64
	Confidence string // exact|estimated|percentOnly|unknown — see the const block
	// Synthetic marks a lane the meter FABRICATED (RateWindow.isSyntheticPlaceholder
	// — set when a provider returned null for a lane and the adapter filled it in).
	// Carried so a made-up lane is never rendered as observed truth.
	Synthetic bool

	// Absolute counters — UsageTotals. Present only when the source really reports
	// them (`exact`); zero otherwise, which Confidence disambiguates.
	Requests          int64
	InputTokens       int64
	OutputTokens      int64
	TotalTokens       int64
	CachedInputTokens int64

	// Money — ProviderCostSnapshot, in minor units (cents). CostCents is what the
	// PROVIDER says this account spent (Claude's extra_usage overage; 0 for a flat
	// subscription — the plan is the charge). CostLimitCents is that meter's budget
	// (ProviderCostSnapshot.limit) — a MONEY cap, never a token quota. Neither is a
	// Hanzo charge: this plane holds no metering client.
	CostCents      int64
	CostLimitCents int64
	Currency       string // ProviderCostSnapshot.currencyCode; "" = unknown
}

Sample is one metering lane's consumption of one provider account at one observation.

TENANCY IS NOT ON THE VALUE. Org and subject are bound by the server from the validated principal at the boundary, so a client cannot assert whose usage this is — the shape makes cross-tenant writes unrepresentable rather than merely refused.

func (Sample) Sanitize

func (s Sample) Sanitize(now time.Time) Sample

Sanitize bounds every field so a warehouse row stays small, finite, and well-formed no matter what a client sends: strings trimmed and length-capped, counters non-negative and clamped, percents coerced into [0,100] even for NaN/Inf, instants bounded to a sane window around `now`, and the two enums defaulted rather than trusted. It is TOTAL (never errors), so the write path always has a safe value — validation of the CLOSED vocabularies (window, kind) is the boundary's job and is a 400, because silently rewriting a caller's window class would corrupt their dash.

It does NOT set the observation clock: the server stamps that, so a client can never backdate a sample or pin a stale one as newest.

type SourceState

type SourceState struct {
	// Available is whether this side's ledger answered. False means its rows are
	// missing, not that there were none.
	Available bool `json:"available"`
	// Scope is whose rows this side carries: "user" or "org".
	Scope string `json:"scope"`
	// Source is the table of record the rows came from.
	Source string `json:"source"`
	// Note is the human sentence that says what this side's numbers mean, so a
	// board cannot present a plan percentage as a Hanzo charge.
	Note string `json:"note"`
}

SourceState is one side of the account board's availability + labelling: which ledger answered, at what scope, and a human note.

type Sources

type Sources struct {
	// Commerce is whether the billing ledger answered the spend block.
	Commerce bool `json:"commerce"`
	// Warehouse is whether the usage warehouse answered the LLM block.
	Warehouse bool `json:"warehouse"`
}

Sources reports which upstreams actually answered, so the console can badge a board as "connected" vs "no data yet" honestly instead of showing a fabricated zero as if it were real.

type Spend

type Spend struct {
	// Available is false when the commerce ledger was unconfigured or
	// unreachable. Every number below is then an honest zero, NOT a measured one.
	Available bool `json:"available"`
	// TotalCents is consumption over the requested window, in US cents. It is
	// self-consistent with ByCategory and Series.
	TotalCents int64 `json:"totalCents"`
	// MTDCents is commerce's authoritative month-to-date consumed figure, which
	// is a different period from the window and is not derived from it.
	MTDCents int64 `json:"mtdCents"`
	// OverageCents is month-to-date consumption beyond the plan's allowance.
	OverageCents int64 `json:"overageCents"`
	// BalanceCents is the prepaid wallet's balance, in US cents.
	BalanceCents int64 `json:"balanceCents"`
	// AvailableCents is what of that balance is still spendable.
	AvailableCents int64 `json:"availableCents"`
	// ByCategory is the window's spend split by ledger category, largest first.
	ByCategory []CategorySpend `json:"byCategory"`
	// Series is the window's spend over time, gap-filled at the window's
	// interval.
	Series []SpendPoint `json:"series"`
	// Source names where the roll-up came from.
	Source string `json:"source"`
}

Spend is the cost roll-up: the genuinely-missing aggregation. TotalCents is the windowed consumption (self-consistent with ByCategory + Series); MTDCents is commerce's authoritative month-to-date figure; Balance/Available are the prepaid wallet the gateway debits. Available=false means commerce was unconfigured or unreachable — honest zeros, never fabricated spend.

type SpendPoint

type SpendPoint struct {
	// T is the bucket's start instant, RFC3339 UTC. Buckets are gap-filled, so a
	// window with no spend still has its points.
	T string `json:"t"`
	// Cents is the consumption recorded in that bucket, in US cents.
	Cents int64 `json:"cents"`
}

SpendPoint is one time bucket of consumption (usage/withdrawal cents).

type Total

type Total struct {
	Source     string
	Scope      string
	Provider   string
	Window     string
	Requests   int64
	Tokens     int64
	CostCents  int64
	UsedPct    float64
	Confidence string
	Windows    int64
}

Total is one provider's usage in the global view, from ONE source at ONE scope.

type TotalView

type TotalView struct {
	// Source is where the row came from: "account" is the provider's own meter
	// on the caller's linked account, "hanzo" is Hanzo-routed inference. The two
	// are never summed.
	Source string `json:"source"`
	// Scope is whose row it is: "user" for the caller's own linked accounts,
	// "org" for the whole tenant's Hanzo-routed usage.
	Scope string `json:"scope"`
	// Provider is the upstream the usage was measured against.
	Provider string `json:"provider"`
	// Window is the meter window class the row rolls up, when it has one.
	Window string `json:"window,omitempty"`
	// Requests is how many requests the row covers.
	Requests int64 `json:"requests,omitempty"`
	// Tokens is the total tokens the row covers.
	Tokens int64 `json:"tokens,omitempty"`
	// CostCents is the row's cost in US cents. For an "account" row this is the
	// PROVIDER's own charge, not a Hanzo one.
	CostCents int64 `json:"costCents,omitempty"`
	// UsedPct is how much of a plan window the row consumed, 0–100. It is a
	// share, never money.
	UsedPct float64 `json:"usedPct,omitempty"`
	// Confidence says how much the counters mean; a percentage-only meter leaves
	// them at zero.
	Confidence string `json:"confidence"`
	// Windows is how many window instances rolled up into the row.
	Windows int64 `json:"windows,omitempty"`
}

TotalView is one row of the account-usage board (the summary's `accounts.rows`). Source and scope are what keep the board honest — see the Source/Scope consts.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL