All pages
Billing and usage
A machine is charged for its processor and memory while it is awake and for its disk while it exists, and for nothing else. The prices are on the pricing page. This page is how the metering works and how to read it.
What is metered, and when
| Quantity | Charged | Counted while |
|---|---|---|
| vCPU-seconds | $0.000005 each | The machine is running. Never while it is suspended, stopped or being created. |
| GiB-seconds of memory | $0.000002 each | The machine is running, at the memory it was given. |
| Disk, in GB-months | $0.04 per GB-month | Always. A volume at its provisioned size, whether or not it is full, as on Fly, so create it at the size you need. Plus what the machine’s checkpoints and its own disk hold in storage. The image a machine was made from is shared and is not counted. |
Each host meters its own machines, opening and closing an interval on every start, sleep and stop, so a machine that wakes for half a second is charged for half a second. A machine that moves to another host is closed on one and opened on the other at the same moment, and is never counted twice. Outbound transfer is not charged, and nothing is charged per machine, app, request or person.
The trial and the free allowance
A new person starts with a one-time trial: $5 of usage at list price, spent before anything else, for 30 days from their first team. After that, every person gets 40 vCPU-hours, 80 GiB-hours and 5 GB-months each calendar month (UTC), with no card. Both are shared by every team that person owns, so opening more teams does not open more of either. A GitHub account younger than 30 days gets a fifth of both until it is that old.
With a card on file the allowance still applies first, and only what goes past it is charged, at the end of the month. A month that comes to less than $1 is not charged.
When the allowance is used, or a budget is reached
A team with no card that has used the allowance is put on hold, and so is a
team whose bill reaches the monthly budget it set itself. On hold, its machines sleep: the
running ones are put to sleep, a sleeping one is not woken by a request or a command, and
nothing new is created, deployed, built or restored for it. A request to one of its
addresses answers 402, and the API answers billing_hold
(errors).
- Nothing is deleted. Every disk, volume, checkpoint and address stays where it was.
- Reading, listing and deleting still work, so a team can make room on its own.
- The hold lifts when a card is added, when the budget is raised or cleared, or when the month turns. The next request wakes each machine as usual.
- A monthly charge that fails is shown in the dashboard and in pilot billing, with a link to pay it. If it is still unpaid seven days later, the team is held the same way until it is paid.
- There is no other limit. No team is held to a number of machines, apps, volumes, builds or checkpoints.
Removing a card
pilot billing card remove (or --remove) asks first,
then charges what the team owes so far this month beyond the free allowance and takes the
card off. The team is back on the free allowance at once, and if it has already used it, its
machines sleep until a card is added or the month turns. Nothing is deleted.
Invoiced and internal teams
Some teams are billed by invoice by arrangement instead of by card. Their usage
is metered and shown exactly the same way, and they are never put on hold. The operator of a
fleet can also mark its own teams as internal, which are metered and shown and never
charged. Only an operator sets either, with pilot billing mode and an admin key,
and every change is recorded with who made it and why.
From the terminal
pilot billing # this month: used, free left, cost, budget, invoices
pilot billing card # opens the page to add a card, then waits for it
pilot billing card remove # charges what is owed so far, then back to the free allowance
pilot billing budget 50 # sleep this team's machines once the month reaches 50 dollars
pilot billing budget --clear
pilot usage --period 2026-10 --group-by label:customer
pilot billing card opens the payment page in your browser,
because a card is something you approve yourself. It is the only step that needs a
browser, and the command waits until the card is on file. A budget can be set by an owner
or an admin of the team.
From the API
curl -H "Authorization: Bearer $PILOT_API_KEY" \
"https://api.pilotrun.app/v1/usage?period=2026-10&group_by=label:customer&scope=fleet"
curl "https://api.pilotrun.app/v1/pricing"
| Parameter | What it does |
|---|---|
period
| One calendar month in UTC, as YYYY-MM. Or pass since and until in unix seconds. |
group_by
|
machine, or label:<key> for the value of one label.
|
scope
|
fleet sums every host. Without it the answer is the host that received the request, and complete says whether every host answered.
|
Every row carries the metered seconds, the billable vcpu_seconds,
gib_seconds and disk_gb_months, and a cost at list price
before the allowance. A key sees its own team’s usage and nobody else’s.
GET /v1/pricing is the price list, the trial and the allowance, and needs no key.
Billing your own customers
A product built on pilots usually keeps every customer’s machines in its own team. Label each machine with the customer it belongs to when you create it, and read usage grouped by that label: each customer’s row is exactly their machines, priced the same way your invoice is. The label is recorded with every interval, so a machine that has been destroyed still counts toward the customer it served. Machines without the label are grouped under an empty value rather than dropped.
Labels count from the moment they are set. To hand a machine you made earlier
to a customer, for example one from a pool of ready machines, change its labels with
PATCH /v1/machines/{id} and a body such as
{"labels": {"customer": "c1", "pool": ""}}. The new labels are merged into the
old ones, and an empty value removes a label. Usage before the change stays with the old
labels and usage after it goes to the new ones, so changing a label never moves usage that
was already recorded.
import { PilotsClient } from '@pilots/sdk'
const pilots = new PilotsClient(process.env.PILOT_API_KEY)
const usage = await pilots.usage.get({ period: '2026-10', groupBy: 'label:customer', fleet: true })
const [team] = Object.keys(usage.orgs)
for (const [customer, row] of Object.entries(usage.groups?.[team] ?? {})) {
console.log(customer, row.vcpu_seconds, row.gib_seconds, row.disk_gb_months, row.cost?.total)
}
const prices = await pilots.usage.pricing()