H-03 Pricing engine
Brick H of the Enterprise AI Platform. Technology: Developed multi-provider pricing engine on eai.price_cards (versioned, effective dates) + eai.fx_rates. French version: ../../fr/bricks/H-03-pricing-engine.md.
Role and responsibilities
- Price any unit of the Usage Event contract: tokens (input, cached input, cache write/read, output, reasoning), images, video seconds, audio seconds, characters, sessions, tool calls, GPU-seconds, storage.
- Select the price card effective at the call date (occurred_at), not the current one.
Position and interactions
- Execution path: synchronous (estimate, measured cost computation).
- Called by POST /v1/finops/estimate, POST /v1/gateway/estimate and by the ledger on ingestion.
Open source and developed parts
- Open source reused: LiteLLM model cost map as a seed only (imported, then versioned and reviewed).
- Developed in the platform: Whole engine: tiered prices, batch discounts, regional prices, reasoning billing modes, FX conversion with decimal arithmetic.
Data model
Control Plane tables (schema eai): eai.price_cards, eai.fx_rates, eai.allocation_policies, eai.usage_events.
Every tenant table has tenant_id uuid NOT NULL and a row level security policy on current_setting('app.tenant_id', true)::uuid. ClickHouse usage_events is the analytics replica of eai.usage_events (ReplacingMergeTree keyed by event_id).
Contract: contracts/v1/usage-event.schema.json (schema_version 1.0). Usage is always reported with contracts/v1/usage-event.schema.json, audit with contracts/v1/audit-event.schema.json.
Estimated, measured and billed cost
Every Usage Event carries three amounts in cost (Usage Event contract, cost.estimated, cost.measured, cost.billed, each a decimal string with a currency or null):
| Amount | When | Source | Used for |
|---|---|---|---|
| Estimated cost | Before execution | Pricing engine (H-03) applied to predicted units (prompt tokens counted, max output tokens, image count, video seconds) | Budget control, quota reservation (H-01), approval thresholds |
| Measured cost | After execution | Pricing engine applied to units really consumed, as reported by the provider response, LiteLLM callback, worker or GPU sampler | Showback, quotas commit, dashboards |
| Billed cost | After reconciliation | Provider invoices and exports imported in /v1/finops/reconciliation/imports | Finance, variance analysis, chargeback |
Rules applied by the pricing engine and the ledger:
- No double counting of reasoning tokens.
pricing.reasoning_billingrecords how the provider bills them:included_in_output(reasoning tokens are already insideoutput_tokens, they are stored inunits.reasoning_tokensfor analysis only and are not priced again),separate(priced with their own rate),not_billed,not_exposed. - Cached tokens.
cached_input_tokens,cache_read_tokensandcache_write_tokensare priced with their own rates of the price card; cached input tokens are subtracted from full price input tokens when the provider reports them insideinput_tokens. - Price applicable at call date. The price card is the one whose effective period contains
occurred_at;pricing.pricing_version,pricing.price_card_idandpricing.effective_atare frozen in the event, so a later price change never rewrites history. - Currency and FX. Amounts keep the provider currency; conversion to the tenant reporting currency uses
eai.fx_ratesof the call date (GET/PUT /v1/finops/fx-rates), and the rate used is stored for audit. - Local model allocation policy. Local models (vLLM, Diffusers, faster-whisper, pyannote, Piper, video GPU workers) have no invoice: their cost comes from an allocation policy (
eai.allocation_policies) combining GPU hours, infrastructure overhead, estimated energy, storage and real usage (GPU-seconds from DCGM samples, tokens served), referenced bypricing.allocation_policy_id. - Event fields. Each event records the tariff applicable at call date, currency, provider, model, region, tenant, agent, session and task (
pricing,provider,model_id,region,tenant_id,agent_id,session_id,task_id), plus project, environment, run, request, provider request and trace ids for correlation. - LiteLLM is a usage source, not the only accounting source. LiteLLM callbacks cover LLM, embeddings and some media calls through providers. Local GPU inference, media workers, tool calls, connectors, sandbox executions and asynchronous operations (batch jobs, video generation) emit their own Usage Events directly to
/v1/finops/usageor/v1/finops/usage/batch.
API endpoints
| Method | Path |
|---|---|
| POST | /v1/finops/price-cards |
| GET | /v1/finops/price-cards |
| POST | /v1/finops/price-cards/{id}/retire |
| GET | /v1/finops/fx-rates |
| PUT | /v1/finops/fx-rates |
| POST | /v1/finops/estimate |
| POST | /v1/gateway/estimate |
POST /v1/finops/price-cards
Request:
{
"provider": "openai",
"model_pattern": "gpt-4o-mini",
"region": "global",
"usage_type": "llm",
"currency": "USD",
"effective_from": "2026-10-01T00:00:00Z",
"reasoning_billing": "included_in_output",
"prices": {
"input_tokens": "0.15",
"cached_input_tokens": "0.075",
"output_tokens": "0.60"
}
}
Response:
{
"id": "pc_31",
"pricing_version": "openai-2026-10-01",
"status": "active"
}
GET /v1/finops/price-cards
Response:
{
"items": [
{
"id": "pc_31",
"provider": "openai",
"model_id": "gpt-4o-mini",
"effective_at": "2026-10-01T00:00:00Z"
}
],
"next_cursor": null
}
POST /v1/finops/price-cards/{id}/retire
Request:
{
"retired_at": "2026-12-01T00:00:00Z"
}
Response:
{
"id": "pc_31",
"status": "retired"
}
GET /v1/finops/fx-rates
Response:
{
"base": "EUR",
"rates": [
{
"currency": "USD",
"rate": "1.0832",
"as_of": "2026-10-29"
}
]
}
PUT /v1/finops/fx-rates
Request:
{
"rates": [
{
"base": "EUR",
"quote": "USD",
"rate": "1.0832",
"as_of": "2026-10-29T00:00:00Z",
"source": "ecb"
}
]
}
Response:
{
"updated": 1
}
POST /v1/finops/estimate
Request:
{
"usage_type": "llm",
"provider": "openai",
"model_id": "gpt-4o-mini",
"units": {
"input_tokens": 12000,
"output_tokens": 2000
}
}
Response:
{
"estimated": {
"amount": "0.0028",
"currency": "EUR"
},
"pricing_version": "openai-2026-10-01",
"fx_rate": "1.0832",
"assumptions": [
"reasoning tokens counted inside output_tokens"
]
}
POST /v1/gateway/estimate
Request:
{
"model": "gpt-4o-mini",
"messages": [
{
"role": "user",
"content": "..."
}
],
"max_tokens": 2000
}
Response:
{
"estimated": {
"amount": "0.0028",
"currency": "EUR"
},
"input_tokens": 12000
}
SDK examples
Python
from enterprise_ai import EnterpriseAI
client = EnterpriseAI(base_url="<control-plane-url>", api_key="eai_live_xxx", tenant="acme", locale="en")
with client.quotas.reservation(cost="1.80", scope={"agent": "agt_ap"}) as r:
result = client.gateway.video(model="video-gen-gpu", prompt="Teaser", seconds=15)
r.commit(cost="1.62") # released automatically on exception
estimate = client.finops.estimate("openai", "gpt-4o-mini", input_tokens=12000, output_tokens=2000)
client.finops.create_price_card({"provider": "openai", "model_pattern": "gpt-4o-mini", "currency": "USD", "effective_from": "2026-10-01T00:00:00Z"})
client.finops.list_price_cards(provider="openai")
client.finops.retire_price_card("pc_31")
client.finops.fx_rates()
client.finops.set_fx_rates({"USD": "0.92", "GBP": "1.17"})
client.finops.estimate("openai", "gpt-4o-mini", input_tokens=12000, output_tokens=2000)
client.gateway.estimate(model="gpt-4o-mini", max_tokens=2000)
TypeScript
import { EnterpriseAI } from "@enterprise-ai/sdk";
const client = new EnterpriseAI({ baseUrl: "<control-plane-url>", apiKey: process.env.EAI_API_KEY, tenant: "acme", locale: "en" });
await client.finops.priceCards.create({ provider: "openai", model_pattern: "gpt-4o-mini", currency: "USD", effective_from: "2026-10-01T00:00:00Z" });
await client.finops.priceCards.list({ provider: "openai" });
await client.finops.priceCards.retire("pc_31");
await client.finops.fxRates.list();
await client.finops.fxRates.put({ rates: { USD: "1.08" } });
await client.finops.estimate({ provider: "openai", usage_type: "llm", model_id: "gpt-4o-mini", units: { input_tokens: 12000, output_tokens: 2000 } });
await client.gateway.estimate({ model: "gpt-4o-mini", maxTokens: 2000 });
Java
EnterpriseAIClient client = EnterpriseAIClient.builder()
.baseUrl("<control-plane-url>")
.apiKey(System.getenv("EAI_API_KEY"))
.tenant("acme")
.locale(Locale.ENGLISH)
.build();
client.finops().createPriceCard(Map.of("provider", "openai", "model_pattern", "gpt-4o-mini", "currency", "USD", "effective_from", "2026-10-01T00:00:00Z"));
client.finops().listPriceCards(Map.of("provider", "openai"));
client.finops().retirePriceCard("pc_31");
client.finops().fxRates();
client.finops().setFxRates(Map.of("rates", List.of(Map.of("base", "EUR", "quote", "USD", "rate", 1.08, "as_of", "2026-10-01T00:00:00Z"))));
client.finops().estimate(Map.of("provider", "openai", "usage_type", "llm", "model_id", "gpt-4o-mini", "units", Map.of("input_tokens", 12000, "output_tokens", 2000)));
client.gateway().estimate(Map.of("model", "gpt-4o-mini", "max_tokens", 2000));
Go
client, err := eai.NewClient(eai.WithBaseURL("<control-plane-url>"), eai.WithAPIKey(os.Getenv("EAI_API_KEY")), eai.WithTenant("acme"), eai.WithLocale("en"))
if err != nil { log.Fatal(err) }
ctx := context.Background()
_, err = client.FinOps.CreatePriceCard(ctx, map[string]any{"provider": "openai", "model_pattern": "gpt-4o-mini", "usage_type": "llm", "currency": "USD", "effective_from": "2026-10-01T00:00:00Z", "prices": map[string]any{"input_per_mtok": "0.15", "output_per_mtok": "0.60"}})
_, err = client.FinOps.ListPriceCards(ctx, eai.WithQuery(map[string]string{"provider": "openai"}))
_, err = client.FinOps.RetirePriceCard(ctx, map[string]any{"price_card_id": "pc_31"})
_, err = client.FinOps.FxRates(ctx, nil)
_, err = client.FinOps.SetFxRates(ctx, map[string]any{"rates": map[string]any{"USD": "0.92"}})
_, err = client.FinOps.Estimate(ctx, map[string]any{"usage_type": "llm", "provider": "openai", "model_id": "gpt-4o-mini", "units": map[string]any{"input_tokens": 12000, "output_tokens": 2000}})
_, err = client.Gateway.Estimate(ctx, map[string]any{"model": "gpt-4o-mini", "max_tokens": 2000})
// usage package: emit a Usage Event from a local worker
emitter := usage.NewEmitter(client, usage.Options{})
defer emitter.Close(ctx)
ev, err := usage.NewEvent("acme", "gpu_local", "worker").Model("local", "llama-70b-local", "", "").Unit("gpu_seconds", 12.4).Unit("energy_wh", 1.7).Build()
if err == nil { err = emitter.Emit(ev) }
// gatewaymw: reserve, call, commit around any http.Handler
http.Handle("/llm", gatewaymw.New(gatewaymw.Config{Client: client, Action: func(r *http.Request) string { return "llm.invoke" }, Reserve: func(r *http.Request) map[string]any { return map[string]any{"cost": "0.50", "tokens": 4000} }})(llmHandler))
Configuration
Environment variables: EAI_DATABASE_URL.
Helm values keys (deploy/helm/enterprise-ai/values.yaml): controlPlane.config, controlPlane.secrets.
Security
- Price cards changes require the finops role and are audited; retired cards stay for historical computation.
- Tenant resolved from the credential, never from the body; PostgreSQL row level security on every tenant table.
- Every mutation and policy decision writes a hash chained audit event (G-11).
- Errors use the standard envelope with message_i18n en and fr.
Observability
- Events priced without a card (pricing_version=unknown) alert.
- W3C traceparent propagated, X-Request-Id returned, spans carry eai.tenant_id, eai.project, eai.agent_id, eai.run_id.
FinOps units
- Money is a decimal string with currency; no floats.
Failure modes and resilience
- No price card for the date -> measured cost null, event flagged, never a guessed price.
Standalone adoption
Can be used alone as a pricing API for internal chargeback.
Acceptance tests
- An event dated before a price change uses the old card.
- reasoning_billing=included_in_output does not add reasoning tokens a second time.