Cost accounting
VectorStep tracks tokens end-to-end (see Team attribution), but tokens aren’t money. This page covers converting them, using either a pricing table you maintain, or live OpenRouter pricing — and how each is persisted so a historical run’s cost never drifts.
VectorStep never bundles a price list. Provider pricing changes often, differs by contract (Azure PTUs, OpenRouter markups, enterprise discounts), and a stale bundled table produces confidently wrong dollar figures — worse than no number at all. There are two ways to get a real, persisted cost: a manual table you maintain, or opting into live OpenRouter pricing — see below.
Three pricing tiers
Section titled “Three pricing tiers”Every priced component (a step’s primary call, its verifier, its grounding-judge) resolves to exactly one of three tiers, in this order:
| Tier | When it applies | Persisted as | Badge colour |
|---|---|---|---|
| Manual | A pricing.models entry matches |
pipeline_steps.cost_manual |
Blue |
| Live, exact | pricing.live_pricing.enabled, and the component’s provider genuinely is openrouter — a match against the exact model string actually called |
pipeline_steps.cost_live_exact |
Green |
| Live, cross-provider | The component ran against a different provider (e.g. anthropic directly), but a live OpenRouter listing for a similar model exists, and pricing.live_pricing.allow_cross_provider is true |
pipeline_steps.cost_live_cross_provider |
Amber |
If none of the three resolves, the component is unpriced. Resolution is a fact, not a preference: given a component’s provider/model and the configured tables, exactly one tier is correct — there is no per-pipeline or per-step override of which tier gets computed. What a pipeline can override is which of these tiers it trusts enough to gate on — see Budget guardrail below.
All three tiers are computed once, at step-save time, and never
recomputed. This is the central change from earlier versions of live
pricing: a historical run’s cost badge shows the same number today as it
did the day the run happened, regardless of how OpenRouter’s catalog has
moved since. pipeline_steps.cost is the sum of whichever tier columns are
non-null for that step.
The manual pricing table
Section titled “The manual pricing table”pricing: currency: USD # display label only — no FX conversion anywhere models: # example — verify against your provider's current pricing before relying on this - match: {provider: anthropic, model: "claude-sonnet-4-6"} input_per_mtok: 3.00 # currency units per 1,000,000 input tokens output_per_mtok: 15.00 - match: {provider: anthropic, model: "claude-haiku"} # prefix match input_per_mtok: 1.00 output_per_mtok: 5.00 - match: {provider: openrouter} # provider-only fallback input_per_mtok: 2.00 output_per_mtok: 8.00 team_budgets: # optional — advisory only, see below payments: 500 platform: 1000Resolution is longest-prefix match, scoped by provider first: filter
entries whose match.provider equals the component’s persisted provider
(or has no provider constraint), then the entry with the longest
match.model that prefixes the component’s model string wins. A
provider-only entry (no model key) is the fallback for that provider if
no model prefix matched.
Live OpenRouter pricing
Section titled “Live OpenRouter pricing”pricing: live_pricing: enabled: true refresh_interval_seconds: 3600 # how often the OpenRouter catalog is re-fetched allow_cross_provider: false # opt-in — see belowWith enabled: true, any component with no manual match is looked up
against OpenRouter’s public catalog (GET https://openrouter.ai/api/v1/models,
no auth required, refreshed on a background schedule). If the component’s
own provider genuinely is openrouter, this is a match against the exact
model that was actually called — as real a rate as manual pricing, just not
typed in by hand — persisted as live, exact (green).
If the component ran against a different provider entirely (e.g. a
direct Anthropic call) and OpenRouter happens to list a similar-sounding
model, allow_cross_provider: true lets that listing price it anyway,
persisted as live, cross-provider (amber). This is a genuinely weaker
claim — a different vendor’s rate, possibly a different contract, matched
by a fuzzy name comparison that ignores version numbers — which is why it
needs its own explicit opt-in on top of live_pricing.enabled, rather than
happening automatically the moment live pricing is on.
Both enabled and allow_cross_provider are operator-level settings in
config.yaml only — there is no per-pipeline override, because whether a
tier gets computed and stored at all is a deployment policy, not an
authoring decision. What a pipeline author controls is a separate question:
how much of that persisted cost they trust enough to let it stop a run.
Mixed-tier steps
Section titled “Mixed-tier steps”A step’s primary, verifier, and grounding-judge calls are priced independently, so they can land in different tiers on the same step — e.g. a primary call priced manually, with a verifier call that fell through to a live cross-provider estimate. Both amounts are persisted in full, on their own columns; nothing is lost or blended. The step’s badge shows the weakest tier present (amber beats green beats blue — a badge must never look more certain than its shakiest contributor) with a ✻ marker, and the hover tooltip itemises every component: its role, its cost, its tier, and (for a live tier) the exact catalog id and per-Mtok rates used at the time.
Budget guardrail
Section titled “Budget guardrail”budget.max_usd works exactly like budget.max_tokens: the runner
accumulates counted cost per completed step and aborts the run with
status=aborted if the total exceeds the ceiling. At least one of
max_tokens/max_usd is required if budget: is present at all; both may
be set together (whichever trips first aborts the run and names which
limit it was).
budget: max_tokens: 50000 max_usd: 5.00 # abort run if counted cost across all steps exceeds this count_pricing: live_exact # optional — see below, defaults to live_cross_providercount_pricing — how much of the persisted cost counts
Section titled “count_pricing — how much of the persisted cost counts”All three tiers are always persisted in full, regardless of count_pricing
— this setting only decides which of that already-real money is allowed to
gate the run. It’s a trust threshold, not a checklist: one value naming
the least-trusted tier still allowed to contribute, drawn from the same
trust order the tiers are already in.
count_pricing |
Counts toward budget.max_usd |
|---|---|
manual |
Only cost_manual — strictest; nothing but rates you typed in yourself can abort a run |
live_exact |
cost_manual + cost_live_exact |
live_cross_provider (default) |
Everything — cost_manual + cost_live_exact + cost_live_cross_provider |
Settable on the pipeline’s budget: block, and overridable per step
(None on a step means “inherit the pipeline’s value”):
steps: - name: risky-step count_pricing: manual # only a manually-priced overrun should stop THIS step's contribution ...A mixed-tier step contributes only the portion of its cost at or above the
threshold — a step with cost_manual: 0.0121 and
cost_live_cross_provider: 0.0022 under count_pricing: live_exact adds
0.0121 to the accumulator and excludes 0.0022. This is the payoff of
persisting the tiers separately rather than as one blended number: the
author’s options aren’t limited to “count all of this step’s cost” or
“count none of it.”
When cost is computed
Section titled “When cost is computed”Once, at step-save time, from whichever tier resolves — never recomputed
later, for any tier. Pricing-table changes and OpenRouter catalog updates
apply going forward only; historical cost reflects what the tokens actually
cost (or were estimated to cost) when they were bought. POST /reload/SIGHUP re-reads the manual pricing table for future steps;
already-persisted costs never change.
Verifier and grounding-judge tokens are priced too
Section titled “Verifier and grounding-judge tokens are priced too”A trust feature that hid its own cost would undermine the point of the
feature. Verifier tokens and grounding-judge tokens are both resolved
against the same three tiers, independently, and folded into the step’s
total cost alongside the primary call — see Mixed-tier steps
above for what happens when they land on different tiers. A grounding:
block that never fired (no trace to check, or the call errored)
contributes nothing at all, same as a step with no verifier configured.
Team budgets
Section titled “Team budgets”pricing.team_budgets sets a per-team currency-units-per-calendar-month
(UTC) figure, surfaced on /ui/insights/teams as a spend-vs-budget bar and
the vectorstep_team_budget_ratio{team} gauge (month-to-date spend /
budget). Month-to-date spend here is the real, persisted total across all
three tiers — no separate “approximate spend” figure any more, since live
pricing is no longer an ephemeral display-time number.
Where cost shows up
Section titled “Where cost shows up”- Run detail — per-step and run-total cost, tier-coloured badges with a full itemised tooltip, and a 4-decimal sub-cent display when a step’s cost rounds to $0.00 at the normal 2 decimals. See Run detail.
- Insights — pipelines/teams/models/providers pages all carry a cost column/card alongside tokens, with a per-source breakdown available. See Insights.
/stats/*JSON endpoints — same rollup functions as the UI, so they can’t disagree; the cost object’sby_sourcebreakdown is additive, existing keys (total,unpriced_steps,currency) are unchanged. See Analytics API.- Prometheus —
vectorstep_pipeline_cost_total{pipeline, team, model, provider, source}, a single counter inpricing.currency’s units, withsourceone ofmanual/live_exact/live_cross_provider. Usesum without(source)for the total, or filter onsourcefor just the tiers you trust. There is no separate approximate-cost metric to remember not to add in. See Observability.
Display formatting is $12.34 for USD, 12.34 EUR otherwise, always via
one template helper.
Where next: token-only gating
Section titled “Where next: token-only gating”If you’d rather not trust any price at all — manual or live — gate on raw
token count instead. budget.max_tokens works independently of pricing
entirely:
budget: max_tokens: 50000 # abort run if accumulated tokens across all steps exceeds thisThis is the right choice when pricing accuracy is itself in question (a
brand-new model with no manual rate yet, or live_pricing deliberately
left off) but you still want a hard ceiling on a runaway pipeline.
max_tokens and max_usd can be set together — whichever trips first
aborts the run and names which limit it was.
Out of scope
Section titled “Out of scope”FX conversion; price history tables (persisted cost is the history);
run-blocking team quotas; Gateway-side pricing (VectorStep is the system of
record for spend); live pricing from any source other than OpenRouter’s
public catalog; per-step cost ceilings (the accumulator is a single
run-level running total checked after each step completes — there is no
“no single step may exceed $X”); team-level count_pricing (deferred — see
Team attribution).