Libraries
Wheelmachine for Python and for TypeScript: the whole API in a few lines, with retries that never make two of anything. Neither needs anything but the language itself.
Python
from wheelmachine import Wheelmachine
client = Wheelmachine() # reads WHEELMACHINE_API_KEY
machine = client.machines.create(name="demo")
machine = client.machines.wait(machine.id, "running") # at once when it already runs
print(client.machines.exec(machine.id, ["uname", "-s"]).stdout)
client.machines.delete(machine.id)TypeScript
import { Wheelmachine } from "wheelmachine";
const client = new Wheelmachine(); // reads WHEELMACHINE_API_KEY
let machine = await client.machines.create({ name: "demo" });
machine = await client.machines.wait(machine.id, "running"); // at once when it already runs
console.log((await client.machines.exec(machine.id, ["uname", "-s"])).stdout);
await client.machines.delete(machine.id);Install#
Python
# Python 3.9 or later; it uses the standard library only
$ pip install ./frontend/sdk/pythonTypeScript
# Node 18 or later, ES modules; it calls the API with the runtime's own fetch
$ cd frontend/sdk/typescript && npm install && npm run build
$ cd /your/project && npm install /path/to/frontend/sdk/typescriptComing The libraries on PyPI and npm. For now they are installed from their source, as above.
The client#
Python
client = Wheelmachine(
api_key=None, # WHEELMACHINE_API_KEY when left out
base_url=None, # WHEELMACHINE_BASE_URL, else https://api.wheelmachine.ai
timeout=60.0, # seconds a call may take; long calls get more on top
max_retries=3, # tries again for refusals that pass and lost connections
proxy=None, # http://host:port; HTTPS_PROXY and NO_PROXY are followed too
)
with client: # or client.close() when done
...TypeScript
const client = new Wheelmachine({
apiKey: undefined, // WHEELMACHINE_API_KEY when left out
baseURL: undefined, // WHEELMACHINE_BASE_URL, else https://api.wheelmachine.ai
timeout: 60_000, // milliseconds a call may take; long calls get more on top
maxRetries: 3, // tries again for refusals that pass and lost connections
fetch: undefined, // a fetch of your own, to go through a proxy
});The key is never logged or printed, and is kept out of errors and out of the client's own printed form. One
Python client is safe to share between threads and keeps its connections open between calls. In TypeScript, every
call takes a last options argument: timeout, maxRetries, signal
(an AbortSignal) and, for calls that make something, idempotencyKey.
Every method#
Methods follow the API one to one. What they take and give keeps the API's own field names
(wait_s, exit_code), so the reference reads for both;
TypeScript's methods are camelCase.
| Python | TypeScript | What it does |
|---|---|---|
machines.create(name=..., cpus=...) | machines.create({ name, cpus }) | Make a machine. |
machines.get(id) | machines.get(id) | One machine. |
machines.list(state=..., labels=...) | machines.list({ state, labels }) | Every machine, newest first, page after page. |
machines.update(id, name=..., labels=...) | machines.update(id, { name, labels }) | Rename it, or change its labels. |
machines.wait(id, "running") | machines.wait(id, "running") | Answer when it is in a state. |
machines.start(id) | machines.start(id) | Run it again. |
machines.pause(id) | machines.pause(id) | Pause it, memory kept. |
machines.stop(id) | machines.stop(id) | Stop it; its files stay. |
machines.archive(id) | machines.archive(id) | Put its files away in storage. |
machines.copy(id, name=...) | machines.copy(id, { name }) | A new machine, a copy of this one. |
machines.snapshot(id, note=...) | machines.snapshot(id, { note }) | Keep its files as they are now. |
machines.exec(id, ["ls"]) | machines.exec(id, ["ls"]) | Run a command; shell= or { shell } for a line of shell. |
machines.read_file(id, path) | machines.readFile(id, path) | Read a file. |
machines.write_file(id, path, data) | machines.writeFile(id, path, data) | Write a file. |
machines.delete(id) | machines.delete(id) | Delete it and its files. |
projects.create(name) | projects.create({ name }) | Make a project; also get, list, update, delete. |
keys.create(name=..., scopes=[...]) | keys.create({ name, scopes }) | Make a key; also list, revoke. |
me() | me() | The account and key in use. |
usage(month="2026-10") | usage({ month: "2026-10" }) | What each machine ran for and cost. |
regions(), gpus() | regions(), gpus() | The catalogue. |
Errors#
Everything a call raises (Python) or throws (TypeScript) is a WheelmachineError, with the API's
status, code and detail, and a class for each family of statuses, such as
NotFoundError or RateLimitError. See Errors
for the list and an example.
Retries that never make two#
A call refused for a moment (429, 503) is tried again after the API's
Retry-After, or after a growing wait with jitter; so is a call that lost its connection. A call that
makes something (a machine, a copy, a snapshot, a project or a key) carries an Idempotency-Key, the
same on every try, so a retry never makes two; give your own (idempotency_key=,
{ idempotencyKey }) to be safe across restarts of your program too. A command, and the actions
(start, pause and the rest), are not sent again once they went out, since they may have
run.
Long calls#
Calls the API holds on purpose (making a machine ready, a wait, a command, archiving) are given the time they need
on top of timeout. In TypeScript, Node's fetch waits at most 300 seconds for an answer, so
waits longer than 240 seconds are asked for in steps, and a command longer than five minutes is best started in the
background, with its output read from a file.
Lists#
Python
for machine in client.machines.list(state="running", labels={"team": "ml"}):
print(machine.id, machine.name)TypeScript
for await (const machine of client.machines.list({ state: "running", labels: { team: "ml" } })) {
console.log(machine.id, machine.name);
}Lists fetch their pages as you go through them.
Files#
A path in a machine begins home/ or work/. A write streams bytes, a file on this
computer, or a stream with its length; a read gives the bytes, or streams them into a file here, which is written
whole or not at all.
Python
from pathlib import Path
client.machines.write_file(machine.id, "work/data.csv", Path("data.csv"))
client.machines.read_file(machine.id, "work/out.csv", to="out.csv")TypeScript
await client.machines.writeFile(machine.id, "work/data.csv", { path: "data.csv" });
await client.machines.readFile(machine.id, "work/out.csv", { to: "out.csv" });Answers#
Answers carry the API's own field names. In Python they are typed objects that keep their JSON in
raw; in TypeScript, plain objects. An answer also says how it came, in last_response
(lastResponse): its request id, whether it was a replay of an earlier call with the same
Idempotency-Key, and the key's rate limit at that moment.
Not in a web page#
The TypeScript library refuses to run in a browser, where the API key would reach everyone who opens the page:
call the API from your server. dangerouslyAllowBrowser: true turns this off, for a page only you
open.