WheelmachineDocs

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)

Install#

Python

# Python 3.9 or later; it uses the standard library only
$ pip install ./frontend/sdk/python

Coming 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
    ...

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.

PythonTypeScriptWhat 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)

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")

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.