Billing developer guide
What this is
Section titled “What this is”How to read prices and cost programmatically: building a price page, showing a customer what they will pay, or reconciling a bill.
There is no billing write API. Nothing here changes what you owe.
Which endpoint answers what
Section titled “Which endpoint answers what”| Question | Call | Auth |
|---|---|---|
| What does every shape cost? | GET /v1/prices | None |
| What is for sale, priced, and in stock right now? | GET /v1/catalog | None |
| What does the same machine cost elsewhere? | GET /v1/prices/comparison | None |
| What am I running, and what has this month cost? | GET /v1/usage | Session |
| Where do I pay? | GET /v1/billing/portal | Session, onboarded |
| Which rated rows make up that total? | The rating service directly | Cloud token |
The first three are public and cached for 60 seconds at the edge.
Building a price page
Section titled “Building a price page”curl -s $BASE/v1/catalog \ | jq '.[] | {name, vcpus, ramMiB, diskGiB, priceEurHour, inStock, aws: .aliases.aws}'const shapes = await (await fetch(`${BASE}/v1/catalog`)).json();
const sellable = shapes .filter(s => s.priceEurHour !== null) // see below .map(s => ({ ...s, monthly: s.priceEurHour * 730 }));730 hours is the month used in our own comparisons; use the same figure if you want your numbers to match ours.
[!warning]
priceEurHour: nulldoes not mean free. It means the rating system has no mapping for that shape yet — it can be launched and nothing will bill for it. That is a defect being fixed. Filter nulls out of a price page rather than rendering “€0”.
inStock is a snapshot, not a reservation
Section titled “inStock is a snapshot, not a reservation”It is computed on every call from live capacity: in stock iff the remaining cores and memory fit one more of that shape. Two consequences for your code:
- It can be up to 60 seconds stale because of the cache, and another customer can take the capacity before you do.
- The launch is the authoritative answer. Handle
InsufficientInstanceCapacityfromRunInstanceseven when the catalog said yes.
Free capacity is clamped at zero, so you will never see negative stock even when the upstream accounting is transiently inconsistent.
Comparison rows
Section titled “Comparison rows”curl -s $BASE/v1/prices/comparison | jq '.comparisons[] | select(.shape=="cd-memory-2-16")'Each row carries provider, usd_hour, egress_included_gb,
egress_usd_per_gb, usd_to_eur, and — the part that matters — source,
as_of and method.
Render the provenance. A comparison without its source and date is a claim; with them it is checkable. The fields are there so you can show them.
Month-to-date cost
Section titled “Month-to-date cost”const usage = await call("/v1/usage"); // session requiredMachines running plus the month’s cost so far, in one call. This is the only cost-before-invoice surface — there is no cost explorer, no budget alert, no per-resource breakdown and no tag allocation to build one from.
Poll it hourly at most: metering runs on an hourly cron, so a tighter loop returns the same number.
An hour is rated about two hours after it ends, so the newest hours are never in this figure. Full detail and what to design around: Usage and cost.
Rounding
Section titled “Rounding”Amounts reach the payment provider in minor units (cents) as an integer. A rated amount under half a cent for a period rounds down to zero — the period is still reported so it is marked as seen, it simply bills nothing.
If you are reconciling hour by hour, expect zero-valued periods and do not treat them as missing data.
Timing
Section titled “Timing”Two delays stack. An hour is rated about two hours after it ends, and metering then emits at most a handful of periods per tick, so a backlog drains over several hours rather than at once.
A cost figure is therefore at least two hours behind reality, and further behind when there is a backlog. Do not build an alert on a threshold that assumes real-time cost, and do not use cost to verify that a shutdown worked — use the running-machines list for that.