# Libraries

> The Python and TypeScript libraries: install, the client, every method, errors, retries that never make two, long calls, lists and files.

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:

```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:

```ts
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:

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

TypeScript:

```sh
# 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/typescript
```

> **Coming:** The libraries on PyPI and npm. For now they are installed from their source, as above.

## The client

Python:

```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:

```ts
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](https://docs.wheelmachine.ai/reference.html) 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](https://docs.wheelmachine.ai/errors/index.html#in-wm-and-the-libraries) 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:

```python
for machine in client.machines.list(state="running", labels={"team": "ml"}):
    print(machine.id, machine.name)
```

TypeScript:

```ts
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:

```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:

```ts
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.
