The API does from your code what you do in the console: order a machine, boot it, reinstall it, destroy it. It takes exactly the same path — same quotas, same credit, same refusals. If the console says no, the API says no, for the same reason.
This guide goes from an empty account to a running machine, in four calls.
1. Create a key
In the console, Account → API keys. Name it after what it
does (CI runners, monitoring) and tick only the
scopes that tool needs. A key that merely watches has no business holding the
right to destroy.
The token is shown once. We keep only its fingerprint: lose it and you revoke it and make another — it cannot be recovered.
2. The first call
Check the key against your own account. This is also the response to read before any order: it says what you can spend.
curl -s https://api.ffxf.net/v1/account \
-H "Authorization: Bearer $FFXF_TOKEN"
{
"data": {
"currency": "CAD",
"credit": { "balance": "124.60" },
"hourly": { "burn_rate": "0.069", "runway_hours": 1805.8, "active_machines": 3 },
"quota": { "limit": 5, "used": 3, "remaining": 2 }
}
}
burn_rate is what your fleet spends per hour,
runway_hours how long that lasts at the current balance. A
script watching those two numbers is never surprised.
3. Pick from the catalogue
Regions, plans and images are readable without a key: you can write your script before you even have an account.
curl -s https://api.ffxf.net/v1/images?plan=nano | jq '.data[].slug'
The ?plan= filter keeps only the images that fit: on the Nano's
20 GB, a Windows image asking for 32 is simply absent from the list, rather
than failing at order time.
4. Order a machine
Start with a dry run. dry_run runs every check — quota, credit,
stock, compatibility — and prices the order without creating anything.
curl -s https://api.ffxf.net/v1/vms \
-H "Authorization: Bearer $FFXF_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"plan":"nano","region":"montreal","image":"debian-13",
"hostname":"runner-01","billing":"hourly","dry_run":true}'
{
"data": {
"would_succeed": true,
"hourly_rate": "0.018",
"month_equivalent": "13.14",
"required_credit": "0.44",
"balance": "124.60",
"runway_hours_after": 1428.0,
"checks": [ { "name": "quota", "status": "pass" }, { "name": "credit", "status": "pass" } ]
}
}
required_credit is 24 hours of usage here: that is the advance
an hourly rental asks for. Drop dry_run and the machine goes
into build.
The Idempotency-Key header is required, and it is not a
formality: if your request times out with no answer, replaying it with the
same key returns the first response instead of creating a second machine.
5. Follow the build
The response carries the machine and an action. Nothing is instant: a disk is cloned, an address reserved, an image booted.
curl -s https://api.ffxf.net/v1/actions/90112 \
-H "Authorization: Bearer $FFXF_TOKEN" | jq .data.status
Poll until the status leaves running. On failure the action
carries the reason — no guessing through logs.
6. Destroy
curl -s -X DELETE "https://api.ffxf.net/v1/vms/4312?confirm=runner-01" \
-H "Authorization: Bearer $FFXF_TOKEN"
The hostname must be repeated in confirm. An id mispasted in a
loop would destroy the wrong machine, and a disk does not come back. Hourly
deletion is immediate, the hour under way is owed, and the meter stops with
the machine.