Skip to main content

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

AmountWhenSourceUsed for
Estimated costBefore executionPricing 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 costAfter executionPricing engine applied to units really consumed, as reported by the provider response, LiteLLM callback, worker or GPU samplerShowback, quotas commit, dashboards
Billed costAfter reconciliationProvider invoices and exports imported in /v1/finops/reconciliation/importsFinance, variance analysis, chargeback

Rules applied by the pricing engine and the ledger:

  • No double counting of reasoning tokens. pricing.reasoning_billing records how the provider bills them: included_in_output (reasoning tokens are already inside output_tokens, they are stored in units.reasoning_tokens for analysis only and are not priced again), separate (priced with their own rate), not_billed, not_exposed.
  • Cached tokens. cached_input_tokens, cache_read_tokens and cache_write_tokens are priced with their own rates of the price card; cached input tokens are subtracted from full price input tokens when the provider reports them inside input_tokens.
  • Price applicable at call date. The price card is the one whose effective period contains occurred_at; pricing.pricing_version, pricing.price_card_id and pricing.effective_at are 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_rates of 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 by pricing.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/usage or /v1/finops/usage/batch.

API endpoints​

MethodPath
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​

  1. An event dated before a price change uses the old card.
  2. reasoning_billing=included_in_output does not add reasoning tokens a second time.

Implementation roadmap