# CLI and SDK

> Rent GPU machines from a terminal with the nodelodge command, or from your own code with the TypeScript and Python SDKs.

The `nodelodge` command and the SDKs for TypeScript and Python call the [Node Lodge API](https://nodelodge.com/docs/api) with an API key. They find a Node Lodge Secure machine, launch a session with a maximum cost, give you the SSH command and stop the session.

## Before you start

1. In the console, open **Keys** and create a key with the scopes `workloads:read`, `workloads:write` and `billing:read`.
2. Set its **Spending limits** (per session and total). Node Lodge refuses any session that would go past one.
3. Have an SSH key pair ready. See [SSH keys](https://nodelodge.com/docs/ssh-keys).

Every session reserves its maximum cost on your organization credit at launch, as its budget. It is billed for the real time it runs, never past the budget. See [Billing](https://nodelodge.com/docs/billing).

## Command line

Requires Node.js 22 or newer.

### Install

```bash
mkdir -p ~/.local/bin && cd ~/.local/bin
curl -fsSLO https://nodelodge.com/downloads/nodelodge.mjs
curl -fsSLO https://nodelodge.com/downloads/nodelodge.mjs.sha256
sha256sum -c nodelodge.mjs.sha256 && mv nodelodge.mjs nodelodge && chmod +x nodelodge
```

On macOS, check with `shasum -a 256 -c nodelodge.mjs.sha256`. Make sure `~/.local/bin` is on your `PATH`.

### Log in

```bash
nodelodge login
```

Paste the key when asked. It is checked, then saved readable by you only, in your configuration folder (`~/.config/nodelodge` on Linux, `~/Library/Application Support/nodelodge` on macOS, `%APPDATA%\nodelodge` on Windows). The key is never typed on the command line, so it stays out of your shell history. In CI, set `NODELODGE_API_KEY` instead. `nodelodge logout` removes the saved key.

### Find a machine

```bash
nodelodge offers --min-vram 48
nodelodge offers --gpu H100 --min-gpus 2
```

Filters: `--gpu <model>`, `--min-gpus <n>`, `--min-vram <GB>`, `--max-price <USD per hour>`, `--all` (machines out of stock too). Offers are listed cheapest first, with their specs, hourly price and location.

### Launch, connect, stop

```bash
nodelodge balance
nodelodge launch 3f0c8f5e-8c3a-4f39-9a8e-2d9b4c1e7a10 --max-cost 20 --allow 203.0.113.7
nodelodge status 9b2d6a1e-1f5c-4a8e-9a77-3c0f2e6d4b21 --wait
nodelodge raise-budget 9b2d6a1e-1f5c-4a8e-9a77-3c0f2e6d4b21 --max-cost 40
nodelodge stop 9b2d6a1e-1f5c-4a8e-9a77-3c0f2e6d4b21
```

- `--max-cost` is required: the most the session may cost, in USD. It is reserved on your credit, and the session stops before it is spent. Add `--hours <n>` to stop it after n hours.
- `--environment <id>` picks the environment, `jupyterlab` by default: data-center machines run `jupyterlab`.
- `raise-budget` raises the budget of a running session to a new `--max-cost`, within the limits of the key.
- `--allow` is the public IPv4 address you connect from, or a range up to /24. Only it can reach the machine over SSH.
- The SSH key is read from `~/.ssh/id_ed25519.pub` (then `id_ecdsa.pub`, `id_rsa.pub`). Pass another with `--ssh-key <file>`. It opens this session only and is not saved on your account.
- `status --wait` waits until the machine runs, printing each launch step as it starts, then prints its `ssh` command. With `--json`, only the session object is printed, with its `launch` field. `status` alone lists your live sessions, `status --ended` your ended ones.
- `stop` ends the session: billing stops and the machine and its disk are deleted. Copy your results off the machine first.

Add `--json` to any command to get the session objects, for scripts:

```bash
id=$(nodelodge launch "$OFFER" --max-cost 20 --allow "$MY_IP" --json | jq -r .id)
nodelodge status "$id" --wait --json | jq -r '.ssh.host'
```

Exit codes: `0` done, `1` refused by Node Lodge (the reason and its code are printed), `2` invalid command line.

## TypeScript SDK

Requires Node.js 22 or newer.

```bash
npm install https://nodelodge.com/downloads/nodelodge-sdk.tgz
```

```ts
import { ConflictError, NodeLodge, PaymentRequiredError } from '@nodelodge/sdk';

const nl = new NodeLodge({ apiKey: process.env.NODELODGE_API_KEY ?? '' });

const { items } = await nl.offers.list({ minVramGb: 48, bookableOnly: true });
try {
  const session = await nl.sessions.launch({
    offerId: items[0].id,
    maxCostCents: 2000,
    sshPublicKey: 'ssh-ed25519 AAAA... you@laptop',
    allowedCidr: '203.0.113.7',
  });
  let s = await nl.sessions.get(session.id);
  while (!s.ssh) {
    await new Promise((r) => setTimeout(r, 10_000));
    s = await nl.sessions.get(session.id);
  }
  console.log(`ssh ${s.ssh.user}@${s.ssh.host}`);
  // ... run your job ...
  await nl.sessions.stop(session.id);
} catch (err) {
  if (err instanceof ConflictError) console.error(err.code, err.message); // API_KEY_LIMIT
  else if (err instanceof PaymentRequiredError) console.error('Add credit in Billing.');
  else throw err;
}
```

Amounts are in cents. `nl.sessions.raiseBudget(id, 4000)` raises the budget of a running session. `launch` takes an optional `environmentId`, and `nl.environments.list({ offerId })` lists what a Community machine can run. `nl.account.funds()` returns the credit available for a new session.

## Python SDK

Requires Python 3.10 or newer.

```bash
pip install https://nodelodge.com/downloads/nodelodge-0.2.0-py3-none-any.whl
```

```python
import os
import time

from nodelodge import ConflictError, NodeLodge, PaymentRequiredError

with NodeLodge(os.environ["NODELODGE_API_KEY"]) as nl:
    offer = nl.offers.list(min_vram_gb=48, bookable_only=True).items[0]
    try:
        session = nl.sessions.launch(
            offer.id,
            max_cost_cents=2000,
            ssh_public_key=open(os.path.expanduser("~/.ssh/id_ed25519.pub")).read().strip(),
            allowed_cidr="203.0.113.7",
        )
        while (s := nl.sessions.get(session.id)).ssh is None:
            time.sleep(10)
        print(f"ssh {s.ssh.user}@{s.ssh.host}")
        # ... run your job ...
        nl.sessions.stop(session.id)
    except ConflictError as e:      # API_KEY_LIMIT
        print(e.code, e.message)
    except PaymentRequiredError:
        print("Add credit in Billing.")
```

Models are typed with snake_case fields, for example `offer.price_per_hour_cents` and `session.max_cost_cents`. `nl.sessions.raise_budget(session.id, 4000)` raises the budget of a running session. `launch` takes an optional `environment_id`, and `nl.environments.list(offer_id=...)` lists what a Community machine can run.

## Sessions in both SDKs

A session has the same fields in both SDKs (camelCase in TypeScript, snake_case in Python):

| Field | Meaning |
|---|---|
| `status` | `booked`, `starting`, `running`, `stopping`, `ended` or `failed`. |
| `maxCostCents` | The budget: the most the session can cost. |
| `costSoFarCents` | Billed so far, the final charge once ended. |
| `heldCents` | The part of the budget still reserved on your credit. |
| `ssh` | Host, user and port once the machine runs. |
| `stopsAt` | When the session stops at the latest. |
| `kind` | `session`, or `reservation` for a machine booked as a reservation. |
| `launch` | While the session starts, its launch steps in order (`ordered`, `allocated`, `booting`, `verifying` on data-center machines, `ready`), each `done`, `active` or `pending`. Null once it runs. The step under way: `currentLaunchStep(session)` in TypeScript, `session.launch_step` in Python. |

## What both SDKs do for you

- **Retries**: reads and launches are retried on network errors, `429` and `5xx`, with exponential backoff, and `Retry-After` is honored. A launch is sent with an idempotency key, so a retry never starts two sessions. Stopping and raising a budget are not repeated after a failure that may have reached Node Lodge: check the session with `get` instead.
- **Typed errors**: `AuthenticationError` (401), `PaymentRequiredError` (402), `PermissionError` (403), `NotFoundError` (404), `ConflictError` (409), `RateLimitError` (429), `ServerError` (5xx), `NetworkError` and `TimeoutError`. Each API error has a stable `code` and a message you can show to a person.
- **A failed start costs nothing**: if the machine cannot start, the session fails, nothing is billed and its budget is released.
- **Sessions and reservations**: on an account that books data-center machines as reservations (`RESERVATION_REQUIRED`), `launch`, `get`, `list` and `stop` work the same way, and `kind` is `reservation`.
- **Your key stays private**: it is sent only to Node Lodge over HTTPS, and never appears in an error message.

To drive sessions from an AI agent instead, see [Use Node Lodge from an AI agent (MCP)](https://nodelodge.com/docs/mcp).
