Skip to content
Raster
Esc
↑↓navigate↵open⌘Jpreview
On this page

Plans, usage and billing

Entitlements, meters, rates, and the current-period summary.

Pricing lives in a server-owned catalog. No price, allowance, limit, weight or provider identifier is hardcoded in any SDK, the dashboard or the marketing site — every one of them reads GET /v1/plans. That is what lets an older SDK display and enforce a newer catalog without being republished, and it is why the numbers below are illustrative rather than authoritative.

The catalog is versioned and effective-dated, because a charge has to stay reconstructible after a price changes: an account records the catalog version its period was priced under, and a summary is rebuilt from that version rather than from whatever is current.

Plans

GET /v1/plans
GET /v1/plans/{plan_id}

developer, pro, startup, enterprise. There is no zero-price plan; see Trials for how a new organization gets in.

Entitlement developer pro startup enterprise
Running machines 3 10 30 negotiated
Machines 5 20 100 negotiated
Snapshots per machine 10 25 50 negotiated
Templates 5 25 100 negotiated
Members 5 25 100 negotiated
Concurrent sessions 10 40 120 negotiated
Largest machine size standard large large negotiated
Included compute (std-hours) 120 400 2000 negotiated
Included hot storage 75 GB 200 GB 1 TB negotiated
Overage allowed yes yes yes yes

null means negotiated, never unlimited. An enterprise organization is refused until an effective-dated override supplies real numbers — an unpriced machine is worse than a blocked one.

Trials

The developer product carries a two-week trial, configured on the billing provider. The provider runs that clock and holds the card; this product records what it is told and never forms a second opinion about when a trial converts.

That means a card is required before an organization can start anything. Signing up opens a billing account in state unsubscribed, which grants nothing — a checkout is what starts the trial.

A trial carries the developer plan’s limits with two differences:

Entitlement developer on trial
Included compute (std-hours) 120 56
Included egress none 50 GB
Overage allowed yes no

These are not about being unable to charge — a card is on file. They bound what a trial cancelled on day thirteen leaves behind: the provider bills nothing for a trial that never converts, so every hour run inside one is an hour this product pays for. 56 standard-hours is 120 prorated to fourteen days, so a trial runs at the same rate as the plan it previews. The egress allowance exists because the developer plan includes none — it bills every byte instead — and a trial that included none could not open a single session.

GET /v1/billing returns trial_ends_at: the provider’s conversion date, present only while the account is trialing.

An unsubscribed or canceled account may create and start nothing, and running machines are stopped with their disks captured. Nothing is deleted. Machines, snapshots, templates and disks stay exactly where they are, and subscribing brings them back untouched. past_due keeps the plan: a failed renewal is a card to fix, not a reason to stop someone’s machines the same hour.

Meters

Six meters, all recorded as exact integers:

Meter Unit What it counts
compute_small_seconds seconds runtime of a small machine
compute_standard_seconds seconds runtime of a standard machine
compute_large_seconds seconds runtime of a large machine
hot_storage_byte_seconds byte-seconds provisioned disk of a live machine
cold_storage_byte_seconds byte-seconds captured disk held in snapshots
egress_bytes bytes bytes served through the gateway

Quantities travel as decimal strings, not numbers. A byte-second over a month exceeds what a double can hold exactly, and a billing quantity that rounds in transport is a billing quantity nobody can reconcile.

Compute is normalized: each size has a weight in thousandths of a standard-hour, and one compute price is multiplied by it. So the per-size rates cannot drift away from each other, and included allowances are expressed in standard-hours regardless of what sizes you actually ran.

A stopped machine still costs storage and no compute. That is the whole trade-off of keeping one around.

Usage

GET /v1/usage           raw records for a period
GET /v1/usage/series    the same records bucketed, for charting
for await (const r of client.usage.list({ periodStart, periodEnd })) {
  r.meter;
  r.size;
  r.quantity;
  r.machine_id;
}

const series = await client.usage.series({ periodStart, periodEnd, meter: "egress_bytes" });

The bucket width is the server’s choice — hourly up to a week, daily past it — and comes back on the response. Buckets with nothing in them are absent rather than zero.

Internal usage records are the source of truth. The billing provider holds a copy; this is the original.

The current period

GET /v1/billing/usage-summary
const summary = await client.billing.current();
summary.status; // "estimate" while the period is open, "final" after
summary.catalog_version; // which catalog priced it
summary.lines; // per meter: quantity, included, billable, amount
summary.compute_credits;

Each line separates raw quantity from included from billable from amount, so a bill is arithmetic a customer can check rather than a number they have to trust. A plan that cannot incur overage prices to zero rather than being hidden — you can still see what you used.

Account, checkout and portal

GET  /v1/billing            the organization's account
POST /v1/billing/checkout   returns a url to send the customer to
POST /v1/billing/portal     returns a customer-portal url

An account state is unsubscribed, trialing, active, past_due or canceled, and carries the plan, the period, trial_ends_at, and the organization’s current limits — each with a key, its current value and its limit.

Every state except unsubscribed comes from the provider. unsubscribed is where an organization starts and has never bought anything; canceled is a subscription that stopped being paid. They are enforced identically and are different things to say to a customer. Both refuse new work with quota_exceeded (HTTP 402) and a quota.limit of subscription.

const url = await client.billing.checkout({ planId: "pro", successUrl });
const portal = await client.billing.portal();

billing:manage is an owner-only permission.

When a limit declines a request

A quota_exceeded error carries a quota object rather than only a sentence:

error.quota.limit; // "running_machines"
error.quota.current; // 3
error.quota.maximum; // 3
error.quota.plan_id; // "developer"
error.quota.upgrade_url; // where to go

Render that, not the message. A caller that shows the sentence instead makes the customer guess which of their limits it was. Limit keys are running_machines, machines, templates, members, concurrent_sessions and snapshots_per_machine.

Running without a provider

BILLING_PROVIDER=none is a first-class mode, not a broken one. Usage accounting, organization limits and the whole trial work with no provider configured — which is what local development and the integration suite run in.

Was this page helpful?