Command-line tool
gpuforge is a small command-line tool for people and AI agents. It searches and rents machines, shows what they cost, opens SSH shells, manages your balance, and can call any API endpoint. It is a single Python file and needs only python3.
Install
curl -fsSL https://api.gpuforge.io/v1/cli/install.sh | shThis downloads the tool to ~/.local/bin/gpuforge (set GPUFORGE_BIN_DIR to choose another folder) and tells you if that folder is not on your PATH. You can read the script first: curl -fsSL https://api.gpuforge.io/v1/cli/install.sh. The tool itself is served at https://api.gpuforge.io/v1/cli/gpuforge and contains no secrets.
Sign in
gpuforge loginThe tool opens a page on the console with a short code. Check the code matches and press Approve while signed in. The tool then receives its own API key and saves it in ~/.config/gpuforge/config.json, readable only by you. Use --no-browser to print the link instead of opening it. A code expires after 10 minutes.
gpuforge whoami # who is signed in
gpuforge logout # revoke this computer's key and forget itConfiguration
| Setting | Where |
|---|---|
| Config file | ~/.config/gpuforge/config.json (or the path in GPUFORGE_CONFIG) |
| API key | Saved by gpuforge login, or the GPUFORGE_TOKEN environment variable (useful for agents and CI) |
| API address | Saved at login, or the GPUFORGE_URL environment variable |
Commands
Add --json to any command for machine-readable output. Commands that cost money ask for confirmation first; add --yes (-y) to skip it once the user has agreed (an agent without a terminal must pass --yes, or the command is refused).
| Command | What it does | Costs money |
|---|---|---|
gpuforge login / logout / whoami | Sign in, revoke this computer's key, show the account | No |
gpuforge search [options] | Find machines that fit | No |
gpuforge caps | List your capabilities | No |
gpuforge caps create spec.json | Create a capability from a JSON file {"name": ..., "spec": {...}} (your user_id is filled in) | No |
gpuforge rent --cap CAP [OFFER_ID] | Rent a machine for a capability: the offer you name, or the cheapest that fits | Yes |
gpuforge status [POD] | Your machines and what they cost right now, or one machine in detail | No |
gpuforge ssh POD [COMMAND...] | Open a shell on a machine, or run one command | No |
gpuforge stop POD | Destroy a machine: billing stops and its disk is wiped | No |
gpuforge billing [--usage HOURS] | Balance, spend, cost now, card, top-up settings; with --usage, cost per machine | No |
gpuforge topup AMOUNT | Add money from the card saved on the console (at least $5; within your daily cap) | Yes |
gpuforge api METHOD PATH [JSON or @file.json] | Call any endpoint, e.g. gpuforge api GET /v1/catalog | Depends |
search options
| Option | Description |
|---|---|
--gpus N | GPUs on one machine, 1–8, exact (default 1) |
--gpu NAME | Card name contains, e.g. h200, b200, 4090 |
--family | Oldest generation: ada_lovelace, hopper, blackwell |
--vram GB | Minimum memory per GPU |
--ram GB | Minimum system RAM |
--disk GB | Disk (default 50) |
--max-price USD | Most per hour for the whole machine |
--provider | vast, runpod, nebius or jarvis |
--cap CAP | Use a capability's requirements instead |
--all | Also show machines where something cannot be confirmed before boot |
--limit N | Rows to show (default 20) |
Offers expire 15 minutes after the search.
rent options
| Option | Description |
|---|---|
--cap CAP | The capability to run (required) |
OFFER_ID | An offer from search; omit it for the cheapest machine that fits |
--name | A name for the machine |
--max-price USD | Refuse above this price per hour |
--no-ssh | Do not add your public key. By default the first of ~/.ssh/id_ed25519.pub, id_ecdsa.pub, id_rsa.pub is added, so gpuforge ssh works |
Before renting, the tool shows your balance and current spend; afterwards, how long the balance lasts at the new rate.
Examples
# Five H200s on one machine, cheapest first
gpuforge search --gpus 5 --gpu h200
# Save a capability and rent the cheapest machine for it
gpuforge caps create my-trainer.json
gpuforge rent --cap cap_ab12cd34
# Watch it, open a shell, stop it
gpuforge status
gpuforge ssh my-trainer-3f9a1c
gpuforge ssh my-trainer-3f9a1c nvidia-smi
gpuforge stop my-trainer-3f9a1c
# Money
gpuforge billing --usage 24
gpuforge topup 20
# Anything else
gpuforge api GET /v1/catalog
gpuforge api POST /v1/jobs @job.jsongpuforge ssh waits up to 3 minutes for SSH to be ready and pins the machine's host key, so the connection is verified.
Using the API from scripts and agents
Everything the CLI does is a plain HTTPS call; any language works.
- Send
Authorization: Bearer <key>on every call andContent-Type: application/jsonon every body. - Give each agent or project its own API key, so you can revoke it alone.
- An agent can read a short guide written for it at
GET https://api.gpuforge.io/v1/agent-guide(plain text, no key needed), and the full reference atGET https://api.gpuforge.io/v1/help. - Treat a machine as temporary: upload inputs after every start or wake, and download results at once.
- Poll with care:
GET /v1/jobs/{id}?wait=60andGET /v1/fleet/{pod_id}every ~10 seconds are enough. - Read
GET /v1/notifications?unread=trueand pass the messages on to the person who owns the account.
A minimal Python job client
import base64, json, os, urllib.request
BASE = os.environ["GPUFORGE_URL"] # https://api.gpuforge.io
KEY = os.environ["GPUFORGE_TOKEN"]
def call(method, path, body=None):
req = urllib.request.Request(
BASE + path, method=method,
data=json.dumps(body).encode() if body is not None else None,
headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=180) as r:
return json.load(r)
job = call("POST", "/v1/jobs", {
"program": os.environ["PROGRAM"], # a program id from GET /v1/catalog
"route": "/v1/generate",
"input": {"prompt": "Full-color photograph of a red bicycle leaning on a brick wall, morning light."},
"max_price_usd": 0.02,
})
while True:
j = call("GET", f"/v1/jobs/{job['job_id']}?wait=60")
if j["status"] in ("done", "failed"):
break
if j["status"] == "done":
open("bicycle.jpg", "wb").write(base64.b64decode(j["result"]["image"]))
print("charged", j["price_usd"])
else:
print("failed, not charged:", j["error"])This page as Markdown, for tools and agents: /docs/cli.md