Skip to content
Beta. VectorStep is in beta.Let us know if something breaks.

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.

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. 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.

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
cache_read_per_mtok: 0.30 # optional — prompt-cache reads, see below
cache_write_per_mtok: 3.75 # optional — prompt-cache writes
- 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: 1000

Resolution 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.

Anthropic bills prompt-cache tokens apart from ordinary input: a cache read costs a fraction of the input rate, and a cache write costs a little more than it. The Gateway marks each agent’s system prompt and tool list as cacheable, so a multi-turn agent normally reads most of its prompt from the cache after the first call. Anthropic’s input_tokens figure does not include those tokens, so they are recorded separately as cache_read_tokens and cache_write_tokens on the step (shown in the run API; input_tokens keeps meaning “fresh input only”).

To price them, give the model’s entry the two optional rates, cache_read_per_mtok and cache_write_per_mtok. Take them from your provider’s pricing page, as with the other rates. They are not derived from input_per_mtok, because the ratio is provider-specific.

  • No cache tokens, no cache rate needed. Models and providers that don’t report cache usage are priced exactly as before.
  • Cache tokens but no matching rate means unknown, not zero. The step’s cost is left NULL (and its run’s total flagged incomplete), rather than quietly counting only the input and output tokens.
  • Live pricing reads the cache rates from OpenRouter’s catalog where the listing publishes them, and treats a listing without them the same way.
  • Writes are priced at the 5-minute rate. The Gateway does not request Anthropic’s 1-hour cache, so every write is a 5-minute write.
  • The token budget is unchanged. budget.max_tokens still counts input and output tokens only; cache tokens count toward max_usd through their cost.
pricing:
live_pricing:
enabled: true
refresh_interval_seconds: 3600 # how often the OpenRouter catalog is re-fetched
allow_cross_provider: false # opt-in — see below

With 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.

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.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_provider

budget.max_usd only counts spend that has a price at or above the count_pricing trust level. A gateway step whose agent’s model has no such price adds nothing to the total, so the budget can never trip on it. POST /pipelines/validate (and the validate_pipeline MCP tool) returns these as warnings — the pipeline is still valid — naming each step, agent and model, and the service logs the same warning when it starts and after each reload. The check asks the Gateway which model each agent uses, and covers the agent’s model_fallbacks as well: a run that fails over to an unpriced fallback spends money the budget never sees, so that is flagged separately (“fallback model …”). If the Gateway is unreachable it reports only the case that needs no model (no pricing configured at all).

A step that fails after the model answered (a reply that isn’t valid JSON, or one cut off at max_tokens) still spent money, so its tokens and cost are saved on the step row and counted toward the budget. With retry:, every attempt that reached the model is priced against its own model and summed onto the one row; the Cost breakdown lists them as attempt_1, attempt_2, and so on. A step with on_failure: policy: continue is counted and the budget checked right after it, so failures alone can trip max_usd. An attempt that never reached the model (a timeout or connection error) has no token data, so it adds nothing — and, as above, a failed attempt on an unpriced model makes the step’s cost unknown rather than a partial sum.

count_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.”

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.

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.

  • 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’s by_source breakdown 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 in pricing.currency’s units, with source one of manual/live_exact/live_cross_provider. Use sum without(source) for the total, or filter on source for 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.

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 this

This 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.

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).