GPU Forge

Capabilities (your own programs)

A capability describes your software once: what hardware it needs, which files to download, how to install and start it, and what you are willing to pay. After that, renting a machine for it, or fifty, is one call with its id, on whichever provider has the cheapest machine that fits.

GPU Forge never interprets your commands or files. They run as root on your own machine.

EndpointPurpose
POST /v1/capabilitiesCreate a capability
GET /v1/capabilitiesList yours
GET /v1/capabilities/{id}Read one
PATCH /v1/capabilities/{id}Change settings, the spec, secrets, on/off, parked machines
DELETE /v1/capabilities/{id}Delete one
POST /v1/catalog/{program_id}/addAdd one of our ready programs to your account as a capability

Create a capability

HTTP
POST /v1/capabilities
FieldRequiredDescription
nameyesYour name for it. Free text
user_idyesYour account id (GET /v1/cli/whoami)
descriptionnoFree text
specyesWhat it needs and how to run it (below)
secretsnoCredentials and secret environment variables (below). Never readable back
Shell
curl -s "https://api.gpuforge.io/v1/capabilities" \
  -H "Authorization: Bearer $GF_KEY" -H "Content-Type: application/json" \
  -d '{
    "name": "my-inference-server",
    "user_id": "usr_...",
    "spec": {
      "version": "1.0.0",
      "min_vram_gb": 48,
      "min_gpu_family": "ada_lovelace",
      "disk_gb": 100,
      "assets": [
        {"url": "https://my-bucket.example.com/server-1.0.0.tar.zst", "dest": "/opt/app", "unpack": true,
         "sha256": "9f2c..."},
        {"url": "https://my-bucket.example.com/weights.safetensors", "dest": "/opt/app/weights.safetensors",
         "sha256": "41ab..."}
      ],
      "install": "cd /opt/app && ./install.sh",
      "start": "cd /opt/app && exec ./serve --port 8000",
      "port": 8000,
      "ready_check": {"path": "/health/ready", "timeout_seconds": 900},
      "max_price_per_hour": 1.5,
      "idle_ttl_seconds": 900
    }
  }'

201 returns the capability with its capability_id (cap_...). Nothing is rented yet.

The spec

Required fields: min_vram_gb, disk_gb, min_gpu_family, start, port. To rent with deploy or scale, the spec also needs max_price_per_hour.

Hardware

FieldDefaultDescription
min_vram_gbrequiredGPU memory per GPU, GB
min_gpu_familyrequiredada_lovelace, hopper or blackwell: that generation or newer
disk_gbrequiredThe whole disk your machine needs, GB, rounded up to the next 50 GB. The container image uses part of it too. Files are downloaded all at once and then unpacked in order (each archive is deleted once extracted), so plan for everything unpacked + every archive, plus the image
gpu_count1GPUs on one machine, exact, 1–8. max_price_per_hour is for the whole machine
min_ram_gb0System RAM, GB. 0 = no filter
min_cpu_ghzMinimum CPU speed reported by the provider
min_cuda_versionLowest host driver CUDA version, e.g. "12.8". Set it if your build needs a recent CUDA: an older host fails only after the machine is rented and your files are downloaded
only_gpusCard names to rent only, e.g. ["RTX PRO 6000"]. Matched on words: case, order and vendor words are ignored
excluded_gpusCard names never to rent, same matching. "rtx 5090" blocks the RTX 5090 everywhere

Price and spending

FieldDefaultDescription
max_price_per_hourThe most one machine may cost you per hour. Required for deploy and scale. Checked before any provider is called
surge_price_per_hourA higher limit used only when nothing fits, or every machine refused, under max_price_per_hour. With price_reduction on, a surge-priced machine is moved to a cheaper one as soon as one appears
daily_budget_usdThe most this capability may cost per UTC day (running and parked machines). Once reached, its machines are parked (or ended where parking is not possible) and new rentals and wakes are refused until 00:00 UTC. Spend is metered every minute, so it can pass the budget by about a minute of running cost
interruptibletrue: rent Vast.ai bid machines, 40–75% cheaper, but a higher bidder can pause the machine and the work on it is lost. For batch and development work, never for something a person is waiting on
deploy_lanestandardHow far above the cheapest fit an automatic deploy may go for a machine on a faster network line: standard 15%, priority 50%. A dearer machine is chosen only if it saves at least 60 seconds of downloading your files
price_reductionoff{"enabled": true, "min_saving_pct": 20, "min_saving_usd_hr": 0.13, "drain_max_seconds": 360}: every few minutes, a running machine is compared with the market; if a machine that fits is cheaper by both amounts, it is rented, and once it is serving the old one is marked retiring_at (send it no new work) and ended when idle or after drain_max_seconds

Software

FieldDefaultDescription
versionYour label, e.g. 1.1 or a git commit. Each machine shows the version it was rented with as capability_version
imageprovider defaultA Docker image. Leave it unset for most programs (see "Images" below)
assetsFiles to download before install: [{url, dest, sha256?, unpack?}] (below)
origins + filesThe same files from a bucket per region (below). Use either assets or origins + files
envEnvironment variables for install and start (not secret; use secrets.env for secrets)
installShell command run once after the files land, as root. Make it safe to run again. A wake skips it when it already succeeded on this disk for this version
startrequiredShell command that starts your program. It should stay in the foreground; GPU Forge runs and supervises it
stopOptional graceful shutdown command. Without it, the process group is ended
portrequiredThe port your program listens on inside the container. capability_url points here
additional_portsOther ports that must be reachable from outside. At most 4 ports in total, port included. A port you do not declare cannot be reached
ready_checkHow GPU Forge can tell your program really works (below). Without it, ready means only that the process started (started_unverified)
busy_checkA JSON field on your port that counts running jobs: {"path": "/status", "field": "jobs.running"}. Shown as busy on each machine; used to drain a machine before it is replaced
reportValues your machine publishes about itself, relayed on every fleet row under report: {"path": "/status", "labels": {"model": "model.name"}, "numbers": {"queue": "queue.length"}}

Idle and access

FieldDefaultDescription
idle_ttl_seconds600Seconds without activity before a machine parks or ends. Cannot be switched off: 0 is stored as 3600
idle_bytrafficWhat counts as activity: traffic (network bytes or GPU use) or gpu (GPU use only, so an open SSH session alone does not keep a machine billing). A busy_check decides over both
ssh_public_keysOpenSSH public keys for root SSH on Vast.ai, RunPod and Nebius. Never send a private key. See Machines

Files: assets

FieldRequiredDescription
urlyesAny HTTP(S) URL the machine can reach, usually your bucket. Machines download directly; the bytes never pass through GPU Forge
destyesAbsolute path where the file lands. Do not use /workspace: on some providers it is shared network storage, not your disk
sha256noFingerprint, checked after download; install stops on a mismatch. Strongly recommended, and required for update-capability
unpacknotrue: extract a tar archive (optionally zstd or gzip) into dest, then delete the archive

Files are downloaded in parallel. Several medium-sized files are easier to cache and republish than one huge file.

Files: origins and files

One file list, served from a bucket per region. The machine's country picks the origin at rent time.

JSON
{
  "origins": [
    {"name": "eu", "zones": ["europe"], "base_url": "https://eu-bucket.example.com/app/"},
    {"name": "us", "zones": ["north_america", "south_america", "any"], "base_url": "https://us-bucket.example.com/app/"}
  ],
  "files": [
    {"path": "server-1.0.0.tar.zst", "dest": "/opt/app", "unpack": true, "sha256": "9f2c..."}
  ]
}

Zones: north_america, south_america, europe, asia (including South-East Asia and Oceania), and any (the fallback). Each zone may appear in one origin only; a machine in a zone no origin covers is skipped unless one origin is any. base_url must end with /; a file's path must not start with /.

ready_check

FieldDefaultDescription
pathrequiredA path on your port that proves the program really works. Point it at something that exercises the real work, not a static health page
methodGETHTTP method
bodyRequest body, sent as is
expect_status200The status that counts as ready
timeout_seconds600How long after start to keep trying before calling it failed. Loading large weights can take minutes

Images

Leave image unset unless you need your own. The provider defaults (a Vast.ai base image and a RunPod PyTorch image) currently provide Ubuntu 24.04, CUDA 13 and Python 3.12, but they are not identical, so bring everything your program needs as files and treat the image as a floor. The one thing you cannot ship is the CUDA driver: use min_cuda_version for that.

On RunPod, GPU Forge's start-up replaces a custom image's own start command, so a custom image's entrypoint does not run unless your start runs it.

What your program is told

These environment variables are set for install and start:

VariableValue
GPUFORGE_MACHINE_IDThe machine's pod_id. Use it as your worker id; never hard-code one
GPUFORGE_CAPABILITYThe capability's name
GPUFORGE_PROVIDERThe provider the machine is on
GPUFORGE_INSTANCE_IDThe provider's own id for the machine

Secrets

Secrets are kept beside the spec, never inside it. They are stored encrypted, never returned by any API call (a read shows only their names), and reach only your own machines.

FieldDescription
secrets.envSecret environment variables, e.g. {"HF_TOKEN": "..."}. They are merged into env on the machine and win over a plain env value of the same name
secrets.asset_authA read-only credential for a private bucket: {"type": "s3", "access_key_id": "...", "secret_access_key": "...", "region": "auto", "expires_seconds": 3600}. Works with Amazon S3, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces, MinIO and Google Cloud Storage (HMAC). Use region: "auto" for R2 and the bucket's real region for S3. Add session_token only for a temporary credential

How the bucket credential works: when a machine asks for its files, GPU Forge turns each file URL into a signed link valid for expires_seconds (default one hour; size it to your download). The credential itself never leaves GPU Forge. Give a read-only credential scoped to the one bucket. Prefer a permanent key: a temporary one (with session_token) expires, and then new machines cannot download their files.

Without asset_auth, a private bucket answers 403 to every download, after the machine is already rented and billing. A capability whose secrets cannot be read is refused before anything is rented.

Changing secrets:

  • Leave secrets out of a PATCH and they stay as they are; a spec change never touches them.
  • Sending env replaces all of env and keeps asset_auth, and the other way round.
  • {} clears all secrets. Clearing is refused while machines depend on them.
  • Running machines read secrets fresh: POST /v1/fleet/{pod_id}/restart-capability applies a rotated secret to a running machine.

A read returns a summary only:

JSON
"secrets": {"set": true, "env_keys": ["HF_TOKEN"], "asset_auth": "s3", "asset_auth_temporary": false}

Change a capability

HTTP
PATCH /v1/capabilities/{capability_id}

Send only what you want to change:

FieldDescription
settingsChange single settings; the rest of the spec stays. Allowed: idle_ttl_seconds, max_price_per_hour, surge_price_per_hour, daily_budget_usd, interruptible, only_gpus, excluded_gpus, min_gpu_family, idle_by, price_reduction, ssh_public_keys
specThe whole spec. It replaces the stored one; anything you leave out is deleted. Read the capability, change what you need, send all of it back. Not together with settings
name, descriptionChange on their own
secretsSee above
activefalse switches the capability off: every new rental and wake is refused (409). Running machines continue until they idle out or you end them
dormant_podsHow many idle machines to keep parked instead of ending them (Vast.ai, Jarvis Labs, Nebius). New capabilities start at 1. Extra parked machines are ended
skip_machinesVast.ai machine ids this capability must never use (a machine's datacenter_id). Sends the whole list; [] clears it
Shell
curl -s -X PATCH "https://api.gpuforge.io/v1/capabilities/cap_ab12cd34" \
  -H "Authorization: Bearer $GF_KEY" -H "Content-Type: application/json" \
  -d '{"settings": {"idle_ttl_seconds": 1200, "max_price_per_hour": 2.5}}'

Running machines are not changed. A change applies to the next machine rented (and, for idle limits, to woken ones). To move a running machine onto your new spec, use POST /v1/fleet/{pod_id}/update-capability; to change one machine's idle limit, use keepalive. See Machines.

A change while a rental for the same capability is in progress answers 409; try again in a moment.

Delete a capability

HTTP
DELETE /v1/capabilities/{capability_id}

Refused (409) while any machine runs it: destroy those first. There is no undo.

Add a ready program to your account

HTTP
POST /v1/catalog/{program_id}/add

Makes your copy of one of our ready programs: a capability on your account that runs our built program, starts from machines that already hold it, and bills your balance like any capability of yours.

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

201:

FieldDescription
capability_idYour copy's id. Use it from now on
name, versionThe program and its current version
gf_api_keyYour program key (gfv_...), shown once. Every request to the program on your machine needs Authorization: Bearer <gf_api_key>
note, nextWhat to do next

Adding the same program twice answers 409 with your existing copy's id.

How a copy differs from your own capability:

  • You choose its settings (price limits, daily budget, idle limit, GPUs, interruptible, price reduction), active and dormant_pods. Its files, commands and environment stay ours; a full spec you send keeps them as they are.
  • The only secret you can set is the program key, {"secrets": {"env": {"GF_API_KEY": "..."}}}. Machines started after the change use the new key.
  • New versions of the program reach your copy by themselves, keeping your settings. Parked machines take the new version at their next wake.
  • A new copy keeps no parked machines (dormant_pods: 0): an idle machine is let go. Parking is your choice (POST /v1/fleet/{pod_id}/park).
  • Parked machines of a ready program are shared between customers, and each start or wake empties the working folder. See How GPU Forge behaves.
  • There is no SSH; use the program's own API.

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