Skip to content
OpenRelay is in early access, and the /v1 API is stable. New capabilities ship in the changelog.
GuidesCompute

VM billing and burn rate

How VM billing works: one bundled hourly rate per VM, locked when it starts, CPU and GPU sizing, and how long your balance lasts before auto-stop.

One bundled hourly rate

Every VM bills at a single hourly rate:

  • GPU VMs: the per-GPU catalog price times the number of GPUs. A 3-GPU VM at $1.37/GPU-hr bills $4.11/hr.
  • CPU VMs: a flat rate for the machine size.

CPU, RAM, and disk are included in that rate. Disk is not billed separately, and there is no per-GB storage charge.

CPU machine sizes

A CPU VM boots with exactly the vCPU and RAM its size carries:

resourceSizevCPURAM
small24 GB
medium48 GB
large816 GB
xl1632 GB

GET /v1/pricing returns the same figures alongside each size's rate (vcpu, ramMb), so read them from there rather than hardcoding this table. resourceSize applies to CPU-only VMs. Omitting it on a CPU VM gives you small; an unrecognised value is rejected with INVALID_RESOURCE_SIZE, and sending it alongside a GPU count is rejected with RESOURCE_SIZE_NOT_APPLICABLE.

GPU VM sizing

A GPU VM is sized from the host capacity its GPUs come with, not from a machine tier. Two fields size it, and omitting either asks the platform to pick:

FieldOmitted (auto)FloorCeiling
guestMemMbThe per-GPU share of the tightest node that can still give a full one65% of the VRAM the VM is placed againstOne node's per-GPU share times gpuCount, and its free RAM right now
diskSizeGb100 GB per GPUThe platform minimum, a flat 50 GBOne node's per-GPU disk share times gpuCount, and its free space, and VM_DISK_MAX_GB

A node's share is the RAM, disk and cores it sells (what it has, less what the host keeps for itself) divided evenly across its own GPUs, times the GPUs you asked for, so the node stays able to run one VM per GPU after yours lands. Auto takes the tightest node that can still give a full share, which keeps the RAM-rich nodes available to requests that need more than a small node's share.

The share bounds you as well as auto. An explicit guestMemMb or diskSizeGb is capped at the node's per-GPU share times gpuCount, and then answered against what is free there: a one-GPU VM on a five-GPU box gets a fifth of the host, however idle the box is, because taking more would leave its other four cards unsellable. A request above every node's share is refused outright; a request inside a share that nothing has free right now is refused as transient.

A VM keeps the guest RAM it was placed with across stops and restarts, with one exception. When the node its disk lives on offers less than that (its per-GPU share has dropped, for example because the host now keeps more RAM for itself), starting the VM after a stop, or re-dispatching it after its job is lost, starts it at that node's current share, as long as the share still clears the floor, and requestedGuestMemMb is updated to match. A fork of a snapshot is admitted the same way. The VM is never moved to another node to keep its size, because its disk stays where it is.

The 100 GB per GPU figure is a default, not a floor: omit diskSizeGb and you get it, name a smaller disk and you get that, down to the platform minimum (minDiskGb on GET /v1/gpu-availability) and whatever the chosen node has room for.

In the dashboard the boot disk is the Storage control beside the operating system. It lists the tiers the fleet actually runs: the platform minimum plus each node's own share for the GPU count you picked, so every choice is a real machine rather than an arbitrary number, and it is pre-selected at 100 GB per GPU. A tier no node can serve at this moment stays listed and is disabled. Guest RAM has no control: the dashboard sizes it at the tightest node's share for your GPU count, steps it down to a tier the fleet can serve right now, and shows that figure in the summary before you deploy.

The dashboard always sends both fields explicitly, pre-selected or not, so the figure it shows is the figure the VM boots with. Omitting them is still how a CLI or SDK caller asks the platform to size a VM; the difference is that the server then resolves them against whichever node it picks, whose per-GPU share may differ from any one figure a form could have displayed beforehand.

GET /v1/gpu-availability publishes what to ask for. Each class carries floorPerGpu and maxPerGpu (the per-GPU range) and offers, one anonymous entry per node with perGpu (its per-GPU share, which is both the auto value and your ceiling), free (what is left on it right now), and the free GPU tiling. A shape is placeable when one node tiles the GPU count and covers the request on both axes, so evaluate a (count, RAM, disk) choice against each offer rather than against fleet totals: a RAM-rich node and a disk-rich node do not add up to a placement. Disk scales linearly, so an N-GPU ceiling is N x perGpu.diskGb; RAM does not, because the hypervisor overhead is not linear in the count, so derive it from perGpu.ramBudgetMb instead of multiplying perGpu.ramMb.

Two details the offers carry that are easy to misread. unitSizes is the tiling any buyer may take and spotUnitSizes is capacity released from a reservation, which only a tier=spot create reaches; a count is placeable at your tier only if it sums out of the lists you can use. And a diskGb of 0 means the node has not reported a live disk reading, so disk is not constraining placement on it: it does not mean the node can hold nothing.

A request no node's share covers is rejected with VM_SHAPE_NOT_PLACEABLE (400) and the message carries the largest share on offer; one inside a share that no node has free right now is INSUFFICIENT_HOST_CAPACITY (503), which clears on turnover. GET /v1/vms/{id} reports guestMemMb and guestVcpu, what the VM is actually running with, alongside the requestedGuestMemMb it was admitted at.

Rate locking

The rate is locked when the VM starts running. A catalog price change never re-rates a running VM; the locked rate is exactly what you are charged at settlement. A stopped VM has no locked rate, so quotes for it use the current catalog price.

Live burn: GET /v1/vms/{id}/burn

Returns the VM's rate, what the current session has cost so far, and your organization's overall burn:

curl https://api.openrelay.inc/v1/vms/{id}/burn \
  -H "Authorization: Bearer or_your_api_key"
{
  "vmId": "4f6b9a1e-9f1c-4c58-a6a3-1f2f0f6f7a10",
  "pricePerHourCents": 411,
  "rateSource": "locked",
  "rateBasis": "gpu",
  "gpuCount": 3,
  "perGpuCents": 137,
  "diskBilled": false,
  "accruedSessionCents": 1028,
  "sessionStartedAt": "2026-07-18T09:12:00Z",
  "orgAccruedCents": 2120,
  "orgAvailableCents": 10430,
  "orgBurnPerHourCents": 414,
  "projectedRunwayHours": 25.07,
  "autoStopFloorCents": 50
}
  • rateSource is locked while a usage session is open (the rate settlement will charge) and catalog otherwise (the rate that would apply on the next start).
  • accruedSessionCents is the cost of the current session so far, computed with the same math as your monthly bill.
  • orgBurnPerHourCents is the sum of the locked rates of everything currently running in your organization.
  • projectedRunwayHours is how long your available balance lasts at the current burn, measured down to the auto-stop floor ($0.50 of available balance). When available balance drops below that floor and auto-recharge cannot top it up, running workloads are stopped automatically. It is null when nothing is accruing.

From the CLI: orl vms burn get <vm-id>.

The same numbers appear in the dashboard on each VM's detail page (hourly rate, session cost) and on the Usage page (org burn rate and runway).

On this page