Skip to main content
Version: 2.x

Pricing model

Pricing decides what a usage event costs. Bursar models it in four layers: an operation declares the inputs, a rate card names a pricing table, a price rule selects a charge, and the PricingEngine evaluates the charge in exact decimal arithmetic.

Operations, measures, and dimensions

An operation is one named billable action — completion, execution, image_generation, whatever your product meters. Each operation declares the inputs its price can reference:

ConceptRoleExample
MeasureA numeric quantity with a unitinput_tokens (unit token)
DimensionA typed selector, not pricedmodel (string)
pricing:
operations:
completion:
measures:
input_tokens: { unit: token }
output_tokens: { unit: token }
cache_read_tokens: { unit: token }
dimensions:
model: { type: string }

Dimensions keep provider-specific attributes (model names, region, batch flags) out of the schema. Measures are non-negative exact numbers; a required dimension must be present on every event for the operation.

Rate cards

A rate card is a named pricing table keyed by operation. Cards can inherit unpriced operations from a parent card via extends, and each plan references exactly one card (plan.rate_card). When a plan selects the card, callers never need to name one; a standalone engine requires rate_card only when more than one card exists.

Price rules and unmatched policies

Each operation on a card lists ordered rules. A rule's when block matches dimensions with typed operators, and the first matching rule wins:

OperatorDimension typeMatch
eqanyExact equality
in / not_inanyMembership / non-membership
prefixstringString prefix
rangenumberBounds gt / gte / lt / lte

When no rule matches, the unmatched policy decides: action: reject refuses the event, or action: charge applies a fallback charge. The canonical config uses both — gpt-4o and gpt-4o-mini get per-million-token rates, anything else falls back to an expression; execution only prices gpt-4o and rejects the rest:

rate_cards:
standard:
operations:
completion:
rules:
- when: { model: { op: in, values: [gpt-4o, gpt-4o-mini] } }
charge:
type: sum
components:
- {
type: per_unit,
measure: input_tokens,
rate: "0.0025",
unit_size: "1000000",
}
- {
type: per_unit,
measure: output_tokens,
rate: "0.0100",
unit_size: "1000000",
}
- {
type: per_unit,
measure: cache_read_tokens,
rate: "0.00125",
unit_size: "1000000",
}
unmatched:
action: charge
charge:
{
type: expression,
formula: input_tokens * 0.005 + output_tokens * 0.015,
}
execution:
rules:
- when: { model: { op: eq, value: gpt-4o } }
charge: { type: per_unit, measure: jobs, rate: "0.04" }
unmatched: { action: reject }

Charge types

A charge computes the credit cost for the event:

TypeWhat it doesExample
flatA constant fee per event{ type: flat, amount: "1.00" }
per_unitRate per unit_size units — the basis for per-million-token pricing{ type: per_unit, measure: input_tokens, rate: "0.0025", unit_size: "1000000" }
packagePrice per fixed block, rounded up/down/nearest{ type: package, measure: jobs, units: "10", amount: "0.10", rounding: ceil }
graduatedMarginal rates per tier{ type: graduated, measure: input_tokens, tiers: [{ up_to: "1000", rate: "0" }, { rate: "0.005" }] }
volumeOne rate selected by total volume{ type: volume, measure: input_tokens, tiers: [{ up_to: "100000", rate: "0.004" }, { rate: "0.003" }] }
expressionAn arbitrary formula over the operation's measures{ type: expression, formula: input_tokens * 0.005 + output_tokens * 0.015 }
sumSum of sub-charges (each a charge)The completion rule above

per_unit with unit_size: 1000000 is how per-million-token rates stay readable: the rate is what one million tokens cost. graduated tiers must end with exactly one open-ended tier, in strictly increasing order; package defaults to rounding up. Graduated and volume tiers differ: graduated applies each tier's rate to the marginal units within it, volume applies the selected tier's rate to the whole amount.

The PricingEngine

PricingEngine is the stateless, database-free evaluation core. Both SDKs construct it from the same canonical document:

from bursar.engine import PricingEngine
from bursar.metrics import UsageMetrics

engine = PricingEngine.from_dict(config) # validates the whole config
cost = engine.calculate(
UsageMetrics(
operation="completion",
measures={"input_tokens": 1000, "output_tokens": 500, "cache_read_tokens": 200},
dimensions={"model": "gpt-4o"},
),
rate_card="standard",
)

The engine:

  • validates measures and dimensions against the operation definition — undeclared names, missing required dimensions, and wrong dimension types raise ConfigError;
  • selects the first matching rule, or the unmatched policy, or rejects;
  • evaluates every charge in exact decimal and quantizes the result to six decimal places with ROUND_HALF_UP;
  • rejects negative or non-finite costs, and never truncates a sub-credit charge to zero.

calculate_batch(metrics, rate_card=...) / calculateBatch(metrics, {rateCard}) evaluates a list of events with the same card selection. get_rate_card_for_plan(plan_key) returns the card a plan references.

Evaluation contract

The engine receives one declared operation, its non-negative measures, typed dimensions, and optional caller metadata. It returns an exact CostBreakdown with the selected rule, rate card, input evidence, and quantized total.

Use the Python pricing reference or TypeScript pricing reference for field-level input and output contracts.

Worked example

One gpt-4o completion with 1,000 input, 500 output, and 200 cache-read tokens against the standard card:

ComponentRateCost
input0.0025 per 1M0.0025 × 1000 / 1000000 = 0.0000025
output0.0100 per 1M0.0100 × 500 / 1000000 = 0.0000050
cache read0.00125 per 1M0.00125 × 200 / 1000000 = 0.00000025
Total0.00000775 → 0.000008

The engine charges exactly 0.000008 credits — the unquantized sum 0.00000775 rounded half-up at six decimal places. The same config returns the same number in Python and TypeScript.