Skip to Content
Task guidesTechnical: Pricing Table

Pricing Table

Technical reference. This page is for integrations, advanced configuration, or platform operations. For portal tasks, start with the user guide.

The pricing table is a platform-scoped registry of input_cents_per_million_tokens and output_cents_per_million_tokens for every (provider, model) pair Orqestra tracks. The org daily-cost pool reads this table to size each reservation; pricing edits are billing-policy events with broad blast radius, so they sit behind a separate permission (Permission.PRICING_MANAGE) auto-granted only to Role.PLATFORM_ADMIN.

The table was added in ADR 0063, backed by Alembic migration 040_governance_pricing_and_snapshot.py. The migration seeds pricing rows for the models your deployment’s model catalog registers, so first traffic post-deploy never observes pricing_unavailable for a model an operator has already approved. See Governed Providers for a worked example of registering a provider and model end to end.

What this surface does

Every cost-acquire resolves pricing cache-first (60s in-process cache, namespace ("pricing", provider, model)) then DB. A miss returns 503 pricing_unavailable and the runtime-gateway converts it to a structured decision.outcome=pricing_unavailable for the caller. The cache is invalidated automatically when an operator PUTs a new pricing row.

Each row uses an active-row supersede pattern: editing a price does not UPDATE the existing row — it INSERTs a new row and sets superseded_at on the prior active row. A partial unique index (WHERE superseded_at IS NULL) keeps exactly one active row per (provider, model) at all times. The table grows monotonically; history is queryable via the dedicated history route.

change_reason is mandatory at PUT time. The route validates min-length 1 + non-whitespace-only, the repository re-validates at the persistence layer, and the audit row carries the reason verbatim. Empty reasons return 400 change_reason_required.

Admin routes

Four routes live under /v1/governance/pricing/.... All four are platform-scoped.

MethodPathPermissionPurpose
GET.../pricingGOVERNANCE_POOL_VIEWList every active (provider, model) pricing row.
GET.../pricing/{provider}/{model}GOVERNANCE_POOL_VIEWRead active pricing for one (provider, model) pair.
PUT.../pricing/{provider}/{model}PRICING_MANAGESupersede the active row + emit v1.governance.pricing_changed + audit row + invalidate the in-process pricing cache.
GET.../pricing/{provider}/{model}/historyGOVERNANCE_POOL_VIEWRead newest-first edit history including superseded rows.

Permission.PRICING_MANAGE is separate from GOVERNANCE_POOL_MANAGE because pricing edits have billing-policy blast radius (every cost reservation across every org reads from this table). The permission is auto-granted to Role.PLATFORM_ADMIN only via the frozenset(Permission) trick — operators with workspace-scope or org-scope governance management cannot edit pricing.

Request / response shapes

GET pricing returns:

{ "pricing": [ { "pricing_id": "8c3a1b9e-0cae-4a63-8ad8-50ee3d5e60f1", "provider": "your-provider", "model": "your-provider/your-model", "input_cents_per_million_tokens": 250, "output_cents_per_million_tokens": 1000, "effective_at": "2026-04-01T00:00:00Z" } ] }

PUT pricing/{provider}/{model} body:

{ "input_cents_per_million_tokens": 250, "output_cents_per_million_tokens": 1000, "change_reason": "Provider announced May-1 price update; pre-staging the new rate." }

Returns the new active snapshot:

{ "pricing_id": "f6c1ab92-1e2a-4f48-9c1c-bc3a2f6a45c0", "provider": "your-provider", "model": "your-provider/your-model", "input_cents_per_million_tokens": 250, "output_cents_per_million_tokens": 1000, "effective_at": "2026-04-27T18:14:02Z" }

400 responses:

  • change_reason_required — change_reason is empty / whitespace-only.
  • input_cents_per_million_tokens / output_cents_per_million_tokens below 0 → 422 (Pydantic). A zero value is valid for local or internally absorbed-cost routes.

GET pricing/{provider}/{model}/history?limit=30 returns newest-first:

{ "history": [ { "pricing_id": "f6c1ab92-...", "provider": "your-provider", "model": "your-provider/your-model", "input_cents_per_million_tokens": 250, "output_cents_per_million_tokens": 1000, "effective_at": "2026-04-27T18:14:02Z" }, { "pricing_id": "8c3a1b9e-...", "provider": "your-provider", "model": "your-provider/your-model", "input_cents_per_million_tokens": 200, "output_cents_per_million_tokens": 800, "effective_at": "2026-04-01T00:00:00Z" } ] }

limit accepts 1..100, default 30.

Example invocations

# List every active pricing row curl -H "Authorization: Bearer $TOKEN" \ "https://control-plane/v1/governance/pricing"
# Read active pricing for a governed (provider, model) pair curl -H "Authorization: Bearer $TOKEN" \ "https://control-plane/v1/governance/pricing/your-provider/your-model"
# Supersede the active row (requires PRICING_MANAGE) curl -X PUT \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"input_cents_per_million_tokens": 250, "output_cents_per_million_tokens": 1000, "change_reason": "2026-05-01 provider list-price update"}' \ "https://control-plane/v1/governance/pricing/your-provider/your-model"
# Pull the newest-first edit history curl -H "Authorization: Bearer $TOKEN" \ "https://control-plane/v1/governance/pricing/your-provider/your-model/history?limit=30"

The same operations are wrapped by the CLI: orqestractl pricing list, orqestractl pricing get, orqestractl pricing put, orqestractl pricing history.

Defaults and configuration

Pricing-related variables on the control-plane Settings:

VariableDefaultPurpose
ORQESTRA_PRICING_CACHE_TTL_SECONDS60Per-worker TTL on the ("pricing", provider, model) cache namespace. PUT pricing invalidates by key.

Pricing rows themselves have no default — the migration seeds pricing for the models your deployment’s model catalog registers at deploy time, and operators add new rows via PUT as new providers / models come online.

Reservation snapshot semantics

Pricing changes do not retroactively re-price open reservations. Each cost-acquire copies the active pricing snapshot into the reservation row at acquire time (Audit P0 #6). A mid-flight pricing supersede affects only acquires AFTER the supersede; existing RESERVED rows commit against their frozen snapshot.

This is the load-bearing invariant for ledger correctness: an operator who updates pricing while traffic is in flight does not produce a “we billed the customer at the old rate but charged the budget at the new rate” mismatch.

Where a price applies

In the portal, Cost > Pricing shows each model’s active price. Expanding a row lists the workloads that spent at that price over the last 30 days, or since the first full day after it took effect, across the workspaces you can view. It also shows 10% of that priced spend, which is the cost of a 10% price change. Calls with no price or not yet settled are counted but never estimated. Spend for earlier versions is not tracked.

Operator alerts

The pricing surface itself does not fire any standalone Prometheus alerts. Pricing edits are explicitly logged via two channels:

  • v1.governance.pricing_changed event — best-effort emission post-supersede with before / after attribute blocks plus change_reason. Subscribers can react to a price change in near-real-time (e.g. update a downstream cost dashboard, page a billing operator on a > 2x price increase).
  • governance.pricing.changed audit row — durable, hash-chained, never redactable per spec §4.2. Carries subject_id=<provider>:<model> plus the same before/after/change_reason payload.

The pricing_lookups_total{provider, model, source} counter exports the cache hit/miss rate; a sudden cliff in cache hits typically indicates either a new model handle the table doesn’t know about or a pricing edit invalidating the cache repeatedly. See Cost & Pricing Recovery Runbook Shape 2  for the audit procedure when a pricing_changed event needs investigation.