Prices: where each number came from, and what it is a number about¶
A cost estimate is worth what its provenance is worth. Every price this package returns says whether it is live, cached or static, and the date it was last checked against the vendor. Three of the vendors below do not sell shots at all, so their per-shot figure is a conversion or a model with a circuit behind it, and the breakdown names which.
Live, cached, static¶
status |
means |
|---|---|
|
fetched from the signed feed in this call, signature verified |
|
served from a verified feed on disk, younger than the TTL (24 hours by default) |
|
the table that shipped with this package |
Nothing fetches at import. Importing qb_compiler, building a compiler, or estimating a cost
opens no socket. The feed is read only when it is asked for: prefer_live=True, --live on the
command line, or QBC_PRICING_LIVE=1. One attempt, five second timeout, then the shipped table
with the reason recorded. Offline behaviour is what it always was, plus a status field.
from qb_compiler.cost import get_pricing_provider
provider = get_pricing_provider() # static, no network
entry = provider.get("ibm_fez")
breakdown = entry.job_cost(4096)
breakdown.usd # 0.65536
breakdown.status # 'static'
breakdown.assumptions["assumed_shots_per_second"] # 10000, and that assumption is ours
qbc pricing show
qbc pricing show --live --json
qbc when circuit.qasm --live
qbc measure-plan h2.json --backend ibm_fez --live
qbc doctor --live
How the vendors actually bill¶
model |
who |
what it needs |
|---|---|---|
|
Braket devices |
shots, and the flat per-task fee |
|
IBM |
shots, plus an assumed throughput, because IBM publishes seconds |
|
IonQ on Azure |
gate counts; without them it falls back to the Braket per-shot price |
|
Quantinuum |
gate and measurement counts; without them it falls back to a per-shot approximation |
IBM publishes Pay-As-You-Go at 96 USD per minute billed per second, which is 1.60 per second, and publishes no per-shot price. The per-shot numbers here are a conversion at a stated throughput; a circuit that runs slower than that costs more than the estimate says.
Quantinuum bills in HQCs: HQC = 5 + C(N1q + 10 N2q + 5 Nm) / 5000 for C shots. Give the
counts and you get the vendor’s own arithmetic; leave them out and you get a per-shot
approximation whose circuit is named in the entry’s notes.
A model that falls back says so in the breakdown, under fell_back_from and fell_back_because.
No estimate silently invents a circuit.
The feed, and what a signature buys¶
The feed is JSON under schema qb.pricing_feed.v1:
{
"schema": "qb.pricing_feed.v1",
"generated_at": "2026-09-18T06:20:10+00:00",
"entries": [{"backend": "ibm_fez", "provider": "ibm", "billing": {"model": "per_second", "...": 0},
"as_of": "2026-09-17", "source": "...", "notes": "..."}],
"signature": {"alg": "ed25519", "key_id": "<fingerprint>", "sig": "<base64>"}
}
The signature covers the canonical JSON of everything except the signature block: sorted keys, no insignificant whitespace, UTF-8. Reordering the entries or changing one digit changes those bytes and breaks the signature. The public key ships in the package, so verification needs nothing from us and no network:
qbc pricing verify pricing.json # exit 0 verified, 2 refused
A feed whose signature does not verify is refused, not used with a warning. The provider falls back to the shipped table and the refusal is in the status reason, so a caller can tell a price that was rejected from one that was merely missing. The same applies to a wrong schema, an unreachable feed, a timeout and a backend the feed does not carry.
Point at a feed with QBC_PRICING_FEED, which takes a URL or a local path:
QBC_PRICING_FEED=./pricing.json QBC_PRICING_LIVE=1 qbc pricing show
A verified feed is cached at ~/.qb-compiler/pricing_cache.json with the time it was fetched, and
served from there until the TTL runs out.
QubitBoost signs and publishes the feed from its own key; the private half never enters this
repository. Until the feed is published at its default URL, --live resolves to the shipped table
with the reason “the feed could not be read”, which is the correct answer and is what the status
field will say.
The shipped table¶
qb_compiler.cost.pricing holds the table, PRICING_AS_OF the date it was last checked, and
get_pricing warns once when that date is more than 90 days old. The warning is doing its job:
treat an estimate from a stale table as indicative and read the vendor page before it decides a
spend.
cost_per_shot(backend) and get_pricing(backend) are unchanged and still read that table, so
nothing that used them behaves differently. New code that wants the assumptions, the provenance
and the vendor’s own billing model uses the provider and the breakdown.