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.