rated_price_usd plus an audit token rate_rule. This is the layer that lets a biller like ShipVoice keep Stripe, invoices, and credits while VoiceGateway produces the rated usage those invoices are built from.
This page covers the rating subsystem: the rate card, how a rule is resolved, where rating runs, and how the rated numbers feed the billing API and the voicegw prices commands.
The rate card
A rate card is a globaldefault_markup fallback plus an ordered list of rules. Each rule is scoped on two axes and carries exactly one kind of arithmetic. It is configured under rate_card: in voicegw.yaml (see the config reference).
Scope fields (all optional, default “any”): modality, provider, model, tenant, plan. A rule with no scope fields matches every request.
Rule kind is one of two:
- cost_plus (
markup): the billable price is the recorded provider cost multiplied bymarkup. Because it multiplies the recorded cost, a cost-plus rule auto-follows voice-prices base movement: when the base price changes, the rated price tracks it with no edit. - fixed (
fixed+unit): the billable price is an advertised$/unitmultiplied by the request’s billable quantity in that unit. A fixed rule is decoupled from the base cost, so it holds a stable advertised price even as the base moves. Valid units:minute,second,char,1k_char,token,1k_token,1m_token,request.
How a rule is resolved
Rating a request resolves the single most specific matching rule. Specificity ranks tenant over plan over global, and model over provider over modality-only:tenant > plan > global, and within that model > provider > modality-only.
Among rules that tie on specificity, the one that appears later in the list wins. That ordering is deliberate: a DB override layered after the YAML seed takes precedence over the seed rule it shadows. When no rule matches, the request falls back to a cost-plus pass at the card’s default_markup.
Write-time, immutable rating
Rating happens at write time, once, on the same path that records cost. Every request row stores two fields:| Field | Type | Meaning |
|---|---|---|
rated_price_usd | float | The billable price for this request. |
rate_rule | str | An audit token naming the rule that produced the price. |
rate_rule token is human-readable and self-documenting: cost_plus:1.3 (recorded cost times 1.3), fixed:0.006/minute (an advertised fixed rate), or default:1 (no rule matched, default markup applied). Because the price and its rule are stamped at write time, they are immutable: editing the card later never rewrites historical rows. Yesterday’s usage stays billed at yesterday’s card, which is what makes the rated numbers safe to invoice against.
Where rating runs
Rating runs server-side, in the gateway’s cost-tracking middleware, when you runvoicegw serve. The active card is loaded from rate_card: and applied as each request row is written. If rating ever fails, it falls back to a cost pass-through so the row is still recorded with a billable price.
The agent-side attach() path stays a cost pass-through by design. Margins are a server-side concern: an agent process records raw cost, and the server (or a hosted cloud that rates on ingest) applies the card. The rating logic lives in the pure voicegateway.billing module, so a hosted cloud can import it and rate on ingest without pulling in the rest of the gateway.
Today, only requests generated by the gateway’s own cost-tracking middleware are rated. Rows pushed to a self-hosted collector’s
POST /v1/ingest (from fleet agents) are persisted with the agent’s pass-through value (rated_price_usd == cost_usd), so fleet-collected usage shows zero margin until the collector re-rates it. Re-rating ingested rows against the collector’s own card is a planned follow-up. Point voicegw prices reconcile and GET /v1/billing/usage at a single-node gateway for accurate margins in the meantime.How rated usage is consumed
Once every row carriesrated_price_usd, two surfaces read it:
- Billing API.
GET /v1/billing/usagerolls rated revenue, recorded cost, and margin up per tenant for a window; passingtenantadds per-(modality, model) line items for invoice detail.GET /v1/billing/rate-cardreturns the card in effect. See the HTTP API reference. voicegw pricescommands.voicegw prices lsprints the card,voicegw prices reconcileflags tenants with thin or negative margins over a window, andvoicegw prices syncchecks fixed rules against the current base cost. See voicegw prices.
Rate-card editing at runtime (a PUT on
/v1/billing/rate-card, and voicegw prices set) is a planned follow-up tied to a DB override store that has not shipped yet. Today the card is the YAML seed; edit voicegw.yaml and reload to change rates.Where to find each piece
| Component | Path |
|---|---|
| Rate card (data + resolution) | src/voicegateway/billing/rate_card.py |
| Rating arithmetic | src/voicegateway/billing/rating.py |
| Margin reconcile + price sync | src/voicegateway/billing/reconcile.py |
| Billing API | src/voicegateway/server/api/billing.py |
voicegw prices command group | src/voicegateway/cli/prices_cli.py |
| Rate card config schema | src/voicegateway/schemas/config_schema.py |