GPU Forge

Machines

A machine is a rented GPU computer running one program: one of our ready programs or one of your own capabilities. The API calls a machine a pod; each one has a unique pod_id such as my-worker-3f9a1c.

Every machine runs a program. To rent a plain machine for interactive work, make a capability whose start keeps running (for example sleep infinity) and rent that; see Capabilities.

EndpointPurposeCosts money
POST /v1/offers/searchFind machines that fitNo
POST /v1/capabilities/{id}/deployRent the cheapest machine that fits a capabilityYes
POST /v1/capabilities/{id}/scaleRun N machines of a capabilityYes
POST /v1/deployRent one offer you pickedYes
GET /v1/fleetYour machinesNo
GET /v1/fleet/{pod_id}One machineNo
GET /v1/fleet/{pod_id}/logsStartup output, events and the last errorNo
GET /v1/fleet/{pod_id}/outputFull program output (ready programs only)No
GET /v1/fleet/{pod_id}/stop-choicesWhat parking would cost per dayNo
POST /v1/fleet/{pod_id}/parkStop it, keep its diskStorage only
POST /v1/fleet/{pod_id}/wakeStart a parked machineYes
POST /v1/fleet/{pod_id}/releaseDone for now: park if possible, else endNo
POST /v1/fleet/{pod_id}/destroyEnd it, disk includedNo
POST /v1/fleet/{pod_id}/keepaliveChange its idle limit and restart the countdownKeeps billing
POST /v1/fleet/{pod_id}/restart-capabilityRestart your software in placeNo
POST /v1/fleet/{pod_id}/update-capabilityRoll your latest capability onto it in placeNo
POST /v1/fleet/{pod_id}/rebootRestart the container in place (Vast.ai)No
GET /v1/fleet/{pod_id}/sshSSH status and commandNo
POST /v1/fleet/{pod_id}/ssh/keysAdd an SSH public keyNo
DELETE /v1/fleet/{pod_id}/ssh/keys/{key_id}Remove an SSH keyNo
GET /v1/gpu-familiesGPU generations and what they includeNo
GET /v1/clusters/options, POST /v1/clusters, ...Multi-node clustersYes

Units everywhere: sizes in gigabytes, prices in US dollars per hour.

Search for machines

HTTP
POST /v1/offers/search

Searches every provider at once and returns every machine that fits, cheapest first. Searching is free and rents nothing.

You can describe the hardware directly, or send a capability_id and the requirements are read from that capability. Anything you send explicitly wins over the capability.

Request fields

FieldDefaultDescription
capability_idTake the requirements from this capability. Give this or the three fields below
min_vram_gbGPU memory per GPU, GB. A card's listed memory is rounded up to the next whole GB before comparing
disk_gbWorking disk beyond the container image, GB. Rounded up to the next 50 GB step
min_gpu_familyOldest GPU generation: ada_lovelace, hopper or blackwell. You get that generation and newer. Older generations and non-NVIDIA cards are never returned
cluster_size1Exact number of GPUs on one machine, 1–8
min_ram_gb0System RAM, GB. 0 = no filter
min_cpu_ghzMinimum CPU speed reported by the provider
max_price_per_hourMost you will pay for the whole machine
only_gpusCard names to return only, e.g. ["RTX PRO 6000"]. Matched on words, so case, order and vendor words do not matter
excluded_gpusCard names never to return, same matching
country_codesISO country codes, e.g. ["US", "DE"]
min_cuda_versionLowest host driver CUDA version, e.g. "12.8"
min_reliabilityHost reliability floor 0–1 (Vast.ai only)
providersallAny of vast, runpod, nebius, jarvis
secure_cloud_onlytrueOnly datacenter-grade hosts. false also returns community hosts, which are cheaper and less accountable
interruptiblefalseVast.ai bid machines: much cheaper, but can be paused by a higher bidder and the running work lost
sortpriceprice, price_per_vram, vram or reliability
limit201–200
include_candidatestrueAlso return machines with a requirement the provider cannot confirm before boot
ssh_public_keysOnly return providers that support managed SSH
Shell
curl -s "https://api.gpuforge.io/v1/offers/search" \
  -H "Authorization: Bearer $GF_KEY" -H "Content-Type: application/json" \
  -d '{"min_vram_gb": 80, "disk_gb": 100, "min_gpu_family": "hopper", "limit": 5}'

Response

FieldDescription
offersMachines where every requirement was verified
candidatesMachines that match everything the provider can prove; each lists its unverified_constraints (for example RAM on providers that do not publish it). Verify those after boot
total, total_candidatesCounts
truncatedMore machines fit than are listed (the dearer ones were dropped)
expires_atWhen the offer_ids in this answer stop working (about 15 minutes)
provisioned_diskThe disk size actually used after rounding
provider_errorsProviders that failed. You still get the others' results

Each offer has offer_id, provider, gpu (name, family, count, vram_gb_per_gpu, vram_gb_total), host (cpu_cores, ram_gb, disk_available_gb, cuda_version), location (country_code), price, availability, secure_cloud, reliability, inet_down_mbps and interruptible.

Price: price.customer_usd_hr is what you pay per hour for that machine, locked when you rent it. When it is null, you pay price.total_usd_hr. Your max_price_per_hour is compared with the price you pay.

A null field means the provider does not publish that value, not that it is zero.

Rent the cheapest machine for a capability

HTTP
POST /v1/capabilities/{capability_id}/deploy

The capability already says what it needs and what it is worth per hour, so no body is needed. GPU Forge first wakes a parked machine of this program if one is free (seconds). Otherwise it searches every provider and rents the cheapest machine that fits under the price limit, moving to the next cheapest if a provider refuses.

Field (all optional)Description
max_price_per_hourOverrides the capability's limit for this rental. Without either, the deploy is refused (400)
nameLabel for the machine. Default: the capability's name
providersRestrict to these providers (skips waking a parked machine)
country_codesRestrict to these countries (skips waking a parked machine)
idle_ttl_secondsOverrides the capability's idle limit for this machine
ssh_public_keysPublic keys for SSH (your own capabilities only)
Shell
curl -s -X POST "https://api.gpuforge.io/v1/capabilities/cap_ab12cd34/deploy" -H "Authorization: Bearer $GF_KEY"
JSON
{
  "pod_id": "hello-gpu-3f9a1c",
  "capability_id": "cap_ab12cd34",
  "provider": "vast",
  "price_usd_hr": 0.48,
  "gpu_name": "RTX 4090",
  "country_code": "DE",
  "offers_considered": 37,
  "attempts": [ ... ],
  "note": "..."
}

The answer comes as soon as the machine is rented, not when it is ready. If the capability has a surge_price_per_hour and every machine under its normal limit refused, the deploy tries once more up to the surge price.

Run N machines

HTTP
POST /v1/capabilities/{capability_id}/scale

desired_pods is a target, not an increment: asking for 3 when 1 is running starts 2; asking again starts nothing. Parked machines are woken first, and only the rest is rented.

FieldDescription
desired_podsRequired. How many machines you want running in total (0–500)
min_podsThe fewest worth having. If fewer can be had, what was rented is ended again (and billed for the seconds it lived)
max_price_per_hourOverrides the capability's limit for every machine in this call
country_codesRestrict to these countries
idle_ttl_secondsOverrides the capability's idle limit
ssh_public_keysPublic keys for SSH

The answer has running_before, newly_rented, running_now, short_by (how many the providers could not supply), pods (each new machine), woken and woken_pods.

scale never ends machines: a target below what is running rents nothing and ends nothing, because a machine in the middle of a request is not GPU Forge's to stop. Machines leave on their idle limit, or when you release or destroy them. A short fill is normal when providers run out: send the same target again later and it fills in. Only one scale or deploy per capability runs at a time; a second one at the same moment answers 409 (try again in a moment).

Rent an offer you picked

HTTP
POST /v1/deploy
FieldRequiredDescription
offer_idyesCopied exactly from a search answer. Expires about 15 minutes after the search (400 after that: search again)
capability_idyesThe capability this machine will run
max_price_per_hourrecommendedChecked against the offer's price before any provider is called; above it answers 409 and nothing is rented
namenoLabel; a random suffix is added to make the pod_id
idle_ttl_secondsnoIdle limit for this machine (default 600)
ssh_public_keysnoPublic keys for SSH
envnoExtra environment variables. These reach the provider; use capability secrets for anything sensitive

The answer has pod_id, provider, instance_id, price_usd_hr, image and notes on what still has to happen.

Watch your machines

HTTP
GET /v1/fleet
GET /v1/fleet/{pod_id}

GET /v1/fleet lists everything that could be costing you money right now, newest first: running, starting, stopping and parked machines. Filters: capability_id, state, provider. ?include=all also returns ended machines; ?include=telemetry adds each machine's last heartbeat (combine with a comma).

GET /v1/fleet/{pod_id} returns one machine with its last heartbeat in full (beat: GPU use, temperature, disks and more).

Fields you will use most

FieldDescription
pod_idThe machine's id
capability_id, capability_name, capability_versionWhat it runs (name and version as they were at rent time)
statedeploying, active, stopped (with parked_at set: parked; without it: stopped by the provider, kept as it is until you start it again or delete it, and you get a notification), failed or terminated
capability_stateYour program: fetching → downloading → installing → starting → ready, or failed. started_unverified = launched but the capability has no ready_check
statusOne sentence for a person: what the machine is doing now and what to expect
progressSix steps that only move forward: renting, booting, downloading, unpacking, installing, starting
capability_errorThe failure in full, only when capability_state is failed
capability_urlWhere your program answers (HTTPS when available)
capability_urlsOne plain HTTP address per declared port
capability_reachableWhy capability_url can or cannot be relied on yet
healthok, warning (missed two heartbeats) or investigating (silent for about two minutes; GPU Forge is asking the provider)
idle_ttl_secondsThe machine parks or ends itself after this many seconds with no activity
provider, country_code, rented_gpu_name, gpu_name, gpu_count, vram_gb_per_gpu, disk_free_gbWhere it runs and what it has (gpu_name and disk are measured on the machine)
sell_usd_hrWhat you pay per hour while it runs, locked at rent. When null, you pay price_usd_hr
parked_atSet while the machine is parked
created_at, capability_ready_at, destroyed_at, destroyed_reasonIts timeline. destroyed_reason is one word, e.g. requested, idle

What each machine costs you right now is also in GET /v1/billing → machines[].cost_usd_hr.

Waiting for ready

Poll GET /v1/fleet/{pod_id} every 10 seconds until capability_state is ready, and pass status on to your user while you wait.

  • A machine that already holds the program: usually 1–3 minutes. A woken parked machine: about 20–60 seconds plus the program's start.
  • A fresh machine: download and install time, up to 15–20 minutes for large programs. Wait at least 20 minutes before giving up.
  • ready is re-checked on every heartbeat: a program that exits later moves to failed.
  • If capability_state is failed or the machine is gone (destroyed_at set), read the logs, then deploy again.

Logs and output

HTTP
GET /v1/fleet/{pod_id}/logs

Returns last_seen_at, events, error and startup (recent output of your program, separate from downloads). It is the last report the machine sent, readable even after the machine has ended; it is not a live stream.

HTTP
GET /v1/fleet/{pod_id}/output?since={time}&limit={n}

For machines running our ready programs: everything the program printed, kept 30 days. Returns pieces (at, run, text) and next (the since value for the next page).

Stop, park, wake

ActionWhat happensDiskBilling after
POST .../parkStops the machine now and keeps its diskKeptStorage price only
POST .../wakeStarts a parked machineKeptFull price
POST .../releaseDone for now: parks it if its program keeps parked machines (dormant_pods) and has a free slot, otherwise destroys itKept or deletedStorage or nothing
POST .../destroyEnds the rental for goodDeletedNothing

Send these as POST with an empty JSON body {}. Destroy is POST /v1/fleet/{pod_id}/destroy, not DELETE.

Park

Shell
curl -s "https://api.gpuforge.io/v1/fleet/<pod_id>/stop-choices" -H "Authorization: Bearer $GF_KEY"
JSON
{
  "pod_id": "hello-gpu-3f9a1c",
  "park": {"allowed": true, "refusal": null, "usd_hr": 0.012, "usd_day": 0.288, "released_after_unused_days": 3},
  "release": {"usd_day": 0.0, "note": "The machine and its disk end now; the next start rents a fresh one."}
}
Shell
curl -s -X POST "https://api.gpuforge.io/v1/fleet/<pod_id>/park" -H "Authorization: Bearer $GF_KEY" \
  -H "Content-Type: application/json" -d '{}'
  • Parking is possible on Vast.ai, Jarvis Labs and Nebius, when the program is switched on and the machine runs the program's current version and disk size. Otherwise it answers 409 with the reason.
  • Parking by hand raises the program's dormant_pods by one if there was no free slot.
  • The provider can take up to a minute to finish stopping the machine.
  • A parked machine unused for 3 days is released; you are notified a day before.

Wake

Shell
curl -s -X POST "https://api.gpuforge.io/v1/fleet/<pod_id>/wake" -H "Authorization: Bearer $GF_KEY" \
  -H "Content-Type: application/json" -d '{}'

You rarely need this: deploy and scale wake parked machines first. A wake that produces no heartbeat within 240 seconds is parked again; after two failed wakes the machine is ended. Read capability_url again after every wake.

Destroy

Shell
curl -s -X POST "https://api.gpuforge.io/v1/fleet/<pod_id>/destroy" -H "Authorization: Bearer $GF_KEY" \
  -H "Content-Type: application/json" -d '{}'

GPU Forge asks the provider to end the machine and records it gone only once the provider confirms. Until then the answer is 502, the request is retried automatically, and the machine stays visible in your fleet, because it may still be billing. Copy anything you need off the disk first.

Idle shutdown and keepalive

Every machine has an idle limit (idle_ttl_seconds, default 600 seconds). After that long without activity it parks (if its program keeps parked machines and the provider can park) or ends.

  • Activity is network traffic or GPU use, measured on the machine. With the capability setting idle_by: "gpu", only GPU use counts, so an open SSH session alone does not keep a machine running.
  • The clock starts only when your program is up.
  • The limit cannot be switched off; a value of 0 is stored as at least one hour.

To keep one running machine longer, or restart its countdown:

Shell
curl -s -X POST "https://api.gpuforge.io/v1/fleet/<pod_id>/keepalive" \
  -H "Authorization: Bearer $GF_KEY" -H "Content-Type: application/json" \
  -d '{"idle_ttl_seconds": 3600, "reason": "long training run"}'
JSON
{"pod_id": "hello-gpu-3f9a1c", "idle_ttl_seconds": 3600, "previous_idle_ttl_seconds": 600, "idle_reset_at": "...", "note": "..."}

The machine keeps billing for as long as it runs. Changing a capability's idle limit affects new and woken machines, not running ones; keepalive is how you change a running one. Answers 409 if the machine is already being shut down or is stopped.

Fix or update a running machine

These keep the same rental, price and disk. Each order is picked up on the machine's next heartbeat (a few seconds), so they work only on a machine that is still reporting.

EndpointUse it whenWhat happens
POST .../restart-capabilityYour program crashed but the machine is fineYour program is stopped and started again from what is on disk. Secrets and env are re-read, so this also applies a rotated secret
POST .../update-capabilityYou changed your capability and want this machine on the new versionThe machine takes the current capability, downloads only files whose sha256 changed, re-runs install and starts. Every file needs a sha256 (422 otherwise). In-flight requests are cut: update one machine at a time
POST .../rebootThe machine is stuckThe container restarts on the same machine; only files not already verified on disk are downloaded. Vast.ai only

Your own capabilities only; machines of our ready programs are updated by GPU Forge.

SSH access

SSH is available for your own capabilities on Vast.ai, RunPod and Nebius. It is not available on machines of our ready programs.

  1. Rent with your public key. Put it in the capability's ssh_public_keys, or send ssh_public_keys on deploy, scale or POST /v1/deploy. Never send a private key. A machine rented without keys has no managed SSH port.
  2. Read the connection details:

    Shell
    curl -s "https://api.gpuforge.io/v1/fleet/<pod_id>/ssh" -H "Authorization: Bearer $GF_KEY"
    JSON
    {
     "pod_id": "hello-gpu-3f9a1c",
     "state": "configured",
     "host": "203.0.113.10",
     "port": 41022,
     "username": "root",
     "command": "ssh -p 41022 root@203.0.113.10",
     "host_public_key": "ssh-ed25519 AAAA...",
     "host_key_fingerprint": "SHA256:...",
     "keys": [{"key_id": "<64 hex characters>", "public_key": "ssh-ed25519 AAAA... you@laptop"}],
     "generation": "...",
     "note": "Key changes affect new connections; already-open SSH sessions remain open. ..."
    }

    state is disabled (rented without keys), pending (being applied; poll again), configured, error or unavailable (the machine is stopped, ended or silent). Pin host_public_key in your known_hosts to verify the machine.

  3. Add or remove keys on a running machine:

    Shell
    curl -s -X POST "https://api.gpuforge.io/v1/fleet/<pod_id>/ssh/keys" \
     -H "Authorization: Bearer $GF_KEY" -H "Content-Type: application/json" \
     -d '{"public_key": "ssh-ed25519 AAAA... teammate@laptop"}'
    
    curl -s -X DELETE "https://api.gpuforge.io/v1/fleet/<pod_id>/ssh/keys/<key_id>" -H "Authorization: Bearer $GF_KEY"

    Changes apply to new connections once state is configured again. Sessions already open stay open.

The CLI does all of this with gpuforge ssh <pod_id>.

What survives what

EventGPU billingFiles on diskInstalled software
ParkStops (storage price only)Kept (ready programs: working folder emptied at the next start or wake)Kept
WakeResumesKeptKept; install is skipped when it already succeeded for this version
Idle limit reachedStopsKept if parked, deleted if endedSame
restart-capabilityContinuesKeptKept
update-capabilityContinuesKept; changed files replacedRe-installed
reboot (Vast.ai)ContinuesKeptRe-installed from verified files
ReleaseStopsKept if parked, deleted if endedSame
DestroyStopsDeletedDeleted
Balance at $0 past the grace periodStopsDeletedDeleted
Parked and unused for 3 daysStopsDeletedDeleted
Provider takes the machine back (interruptible, or a failure)StopsLostLost
Parked machine's GPU given to someone else–The wake fails and a new machine is rented–

Keep your own copy of anything that matters. Machines are rented from GPU marketplaces and can fail or be reclaimed.

GPU families

HTTP
GET /v1/gpu-families

Lists the supported generations (ada_lovelace, hopper, blackwell), the minimum CUDA compute capability of each, and which generations satisfy each minimum. The order is a policy line, not a speed ranking.

Several GPUs and clusters

  • Up to 8 GPUs on one machine: set gpu_count (1–8) on the capability, or cluster_size on a search. It is exact, not a minimum, and max_price_per_hour is for the whole machine. Prefer this to a cluster.
  • Multi-node clusters (training across machines, RunPod): GET /v1/clusters/options lists cards available for a cluster now with an estimated price. POST /v1/clusters with capability_id, gpu_type, nodes (2–8), gpus_per_node (1–8) and max_price_per_hour (for the whole cluster) rents all nodes at once. Each node runs the capability's start with NODE_RANK, NUM_NODES, MASTER_ADDR, MASTER_PORT and WORLD_SIZE set, and appears in your fleet as <cluster_id>-n<rank>. GET /v1/clusters and GET /v1/clusters/{cluster_id} show them; DELETE /v1/clusters/{cluster_id} ends every node. A cluster also ends once every node has been idle past its idle limit.

This page as Markdown, for tools and agents: /docs/machines.md