Skip to content

The output contract (v1.0)

Every price costQL produces (at any tier, on any query, predicted or measured) follows one frozen, stable shape. This is the contract an application budgets against: it is enforced by costql.contract.validate() (dependency-free), and anything that fails validation is, by definition, a contract violation. The contract is demonstrated against the real TMDB demo at every tier in packs/contract_examples.json (last run: 18 examples, 0 violations).


price is always present, a number >= 0, in currency cost-units, safe to bill on, at every tier and on every query. costQL never refuses to price.

price is the billable number: a conservative ceiling for a predicted quote (it never under-prices) or the exact total for a query that ran. Everything else in the result is explanatory detail. An app integrates once against the core and simply gains richer breakdowns as the API’s instrumentation improves. It never has to change how it reads the number it charges on.

Cost-units only, never dollars: the consuming app owns the single rate that turns cost-units into money. costQL prices; the app bills.


basiswhenprice means
predictedbefore the query runs: a quote from the static pricing packsafe ceiling (never under-prices); typical_price is the fair average estimate
measuredafter the query ran: an exact receipt from the real tracethe exact work the run caused; confidence is always exact

Three tiers = how sharply cost could be resolved

Section titled “Three tiers = how sharply cost could be resolved”

A tier is the fidelity the API’s instrumentation affords (see One currency, three fidelities). It changes what detail the result carries, not whether you get a billable price.

detail sectionT1T2T3
price, currency, confidence, tier, basis, schema_hash, caveats
typical_price
breakdown: per-resolver cost
sharing: observed dedup / cache
external_costs: named paid/3rd-party hosts
  • T1: a single total only. When that is all the API affords, the total is a wall-clock proxy (currency: wall_time_ms); work hidden by parallelism/batching is not decomposed and tends to under-count (see the genre-hub example: measured T3 156 ms of work → T1 87 ms of elapsed time).
  • T2: per-resolver work (currency: work_ms), but sharing is inferred, so no observed sharing section. (A measured T2 receipt carries no breakdown either: the run reported a total without a per-resolver split; a predicted T2 quote does carry one, because the model can decompose.)
  • T3: per-resolver work plus observed sharing (which loads coalesced or hit cache) plus external_costs (paid hosts named, carrying the seller’s authored per-call fee).

The validator enforces the gating: e.g. an observed sharing section at T1 or T2 is a contract violation.


Core (mandatory, every result):

  • contract_version (string): "1.0".
  • tier (string): "T1" | "T2" | "T3".
  • basis (string): "predicted" | "measured".
  • currency (string): cost-unit; "work_ms" (T2/T3, and measured T1 when work-ms is present) or "wall_time_ms" (T1 wall proxy).
  • price (number ≥ 0): the billable number (see the guarantee above).
  • typical_price (number | null): fair average estimate; equals price for a measured receipt.
  • confidence (string): "high" | "medium" | "low" for a quote (predictability, orthogonal to tier; cyclic queries are flagged low), "exact" when measured.
  • schema_hash (string): which schema/pack produced this.
  • caveats (string[]): human-readable notes (may be empty).

Tier-gated detail (optional; present only where the table above allows):

  • breakdown (object[]): { resolver_id, cost, invocations, [list_size] }.
  • sharing (object[]): observed sharing. Inner shape differs by basis (honest asymmetry): predicted { loader, folds:[resolver…], counted_once }; measured { loader, requested, calls, saved, cache_hits, external }.
  • external_costs (object[]): { resolver_id, host, authored_fee, measured_fee:false }; the per-call fee is authored by the seller in cost-units, never measured.

A predicted result also echoes query (convenience, not part of the core).


Predicted · T1: total only, wall-clock proxy ({ person(id:"6193"){ filmography{ movie{ title } } } }):

{
"contract_version": "1.0", "tier": "T1", "basis": "predicted",
"currency": "wall_time_ms", "price": 56.16, "typical_price": 50.11,
"confidence": "high", "schema_hash": "26c786209ec27586", "caveats": []
}

Predicted · T3: same query, now with per-resolver breakdown and observed sharing:

{
"contract_version": "1.0", "tier": "T3", "basis": "predicted",
"currency": "work_ms", "price": 69.35, "typical_price": 55.18,
"confidence": "high", "schema_hash": "26c786209ec27586", "caveats": [],
"breakdown": [
{ "resolver_id": "Query.person", "cost": 49.62, "invocations": 1, "list_size": 1 },
{ "resolver_id": "Person.filmography", "cost": 0.02, "invocations": 1, "list_size": 20 },
{ "resolver_id": "Credit.movie", "cost": 0.29, "invocations": 1, "list_size": 1 }
],
"sharing": [
{ "loader": "/movie/{id}", "folds": ["Credit.movie"], "counted_once": true },
{ "loader": "/person/{id}/movie_credits", "folds": ["Person.filmography"], "counted_once": true }
]
}

Predicted · T3 with a paid host ({ movie(id:"27205"){ aiSummary } }): external_costs names it for the authored fee:

{
"contract_version": "1.0", "tier": "T3", "basis": "predicted",
"currency": "work_ms", "price": 40.61, "typical_price": 32.31,
"confidence": "high", "schema_hash": "26c786209ec27586", "caveats": [],
"breakdown": [ { "resolver_id": "Query.movie", "cost": 29.69, "invocations": 1, "list_size": 1 } ],
"external_costs": [
{ "resolver_id": "Movie.aiSummary", "host": "api.anthropic.com", "authored_fee": 0.0, "measured_fee": false }
]
}

Measured · one run, three fidelities ({ movie(id:"27205"){ cast(limit:8){ person{ name } } } }): the same execution downgraded per tier:

// T3: per-resolver + observed sharing (8 person loads fully coalesced: requested 8 -> 0 calls)
{ "tier": "T3", "basis": "measured", "currency": "work_ms", "price": 41.64, "confidence": "exact",
"breakdown": [ {"resolver_id":"Movie.cast","cost":41.64,"invocations":1},
{"resolver_id":"Credit.person","cost":0.0,"invocations":8} ],
"sharing": [ {"loader":"/movie/{id}/credits","requested":1,"calls":1,"saved":0,"cache_hits":0,"external":false},
{"loader":"/person/{id}","requested":8,"calls":0,"saved":8,"cache_hits":0,"external":false} ] }
// T2: request total only (trace carried no per-resolver split)
{ "tier": "T2", "basis": "measured", "currency": "work_ms", "price": 41.64, "confidence": "exact",
"caveats": ["no per-resolver breakdown in trace -> T2 ..."] }
// T1: wall-clock elapsed proxy
{ "tier": "T1", "basis": "measured", "currency": "wall_time_ms", "price": 48.08, "confidence": "exact",
"caveats": ["no work-ms in trace -> T1 (wall-clock elapsed proxy) ..."] }

Validate any pack’s output yourself with costql validate --pack <pack.json>, or programmatically via from costql.contract import validate.


contract_version is "1.0". Additive, backward-compatible changes (new optional fields) keep "1.0". Any change that removes or repurposes a field, or alters the core guarantee, bumps the major version. Consumers should read the core fields by name and treat unknown fields as forward-compatible additions.