---
sidebar_position: 2
---

> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lium.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Developer Quickstart

## Get started in 5 min

This guide takes you from zero to your first authenticated API call against the Lium platform API.

### Step 1 — Get an API key

1. Log in to [lium.io](https://lium.io).
2. Click **Access** in the sidebar, then **API Keys** ([lium.io/api-keys](https://lium.io/api-keys)).
3. Click **ADD NEW**, copy the key, and store it securely — you can copy it again later from the same table.

No browser? [`lium signup`](./cli/reference/signup.md) creates the account and its key from the terminal; [`lium init --no-browser`](./cli/reference/init.md) does the same for an existing account.

### Step 2 — Make your first request

List your pods:

```bash
export LIUM_API_KEY=your_api_key_here

curl https://lium.io/api/pods \
  -H "X-API-Key: $LIUM_API_KEY"
```

### Step 3 — Pick a node and a template

Renting happens on a node, so list what is available and take an id. Prices are per GPU per hour, in `price_per_gpu`:

```bash
curl https://lium.io/api/executors \
  -H "X-API-Key: $LIUM_API_KEY" \
  | jq 'sort_by(.price_per_gpu) | .[0] | {id, machine_name, gpu_count, price_per_gpu}'
```

You also need a template, which decides the image the pod boots:

```bash
curl https://lium.io/api/templates \
  -H "X-API-Key: $LIUM_API_KEY" \
  | jq '.[] | select(.name | contains("Pytorch")) | {id, name}'
```

### Step 4 — Rent the node

```bash
curl -X POST https://lium.io/api/executors/<executor_uuid>/rent \
  -H "X-API-Key: $LIUM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pod_name": "my-first-pod",
    "user_public_key": "'"$(cat ~/.ssh/id_ed25519.pub 2>/dev/null || cat ~/.ssh/id_rsa.pub)"'",
    "template_id": "<template_uuid>",
    "termination_hours": 1
  }'
```

The key you send is the one you will connect with, so send a public key you hold the private half of. `pod_name` and `user_public_key` are required, and you must pass exactly one of `template_id` or `dockerfile_content`. `termination_hours` (1–720) is your safety net: the pod terminates itself after that many hours even if nothing else does.

Renting needs a balance above 15 minutes of the node's price (`price_per_gpu × gpu_count / 4`); below that the call returns `403` with the required amount in the message.

The response is `{"success": true, "pod_id": "<uuid>"}`.

**Retrying safely.** Send an `Idempotency-Key` header (1–255 letters, digits, `-`, `_`, `.` or `:`; a UUID is fine) and reuse it when you retry after a lost answer:
- Within 24 h, the same key with the byte-identical body returns the first call's answer (same `pod_id`) with `Idempotent-Replayed: true`, instead of a second pod.
- A different body with that key is 422 `idempotency_key_reused`.
- A repeat while the first call is still running is 409 `idempotency_in_progress`; retry after `Retry-After`.
- If the first call failed (a 5xx or an error) or its answer could not be recorded, it may have rented. The key is then held with no expiry, and every repeat gets 409 `idempotency_outcome_unknown`. Check `lium ps` for the pod before you send the request again with a new key.

Without the header nothing changes. `POST /executors/rent-by-spec` and `POST /executors/cluster/rent` take the same header when `GET /version` lists `"rent_idempotency"` under `features`; on a backend that does not list it, those two routes ignore the header and a retry rents again. The codes are in [Error codes](/developers/error-codes).

### Step 4, the short way — rent by spec

If what you want is a spec — "one H100, cheapest, under $2.50 per GPU-hour" — and any node that meets it will do, skip Step 3 and let the API pick: `POST /executors/rent-by-spec` selects and rents in one call.

```bash
curl -X POST https://lium.io/api/executors/rent-by-spec \
  -H "X-API-Key: $LIUM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "gpu_type": "H100",
    "gpu_count": 1,
    "max_price_per_gpu_hour": 2.5,
    "pod_name": "my-first-pod",
    "user_public_key": "'"$(cat ~/.ssh/id_ed25519.pub 2>/dev/null || cat ~/.ssh/id_rsa.pub)"'",
    "termination_hours": 1,
    "dry_run": true
  }'
```

The body takes everything `POST /executors/{id}/rent` takes (`pod_name`, `user_public_key`, `volume_id`, `initial_port_count`, `termination_hours`, `dockerfile_content` …) minus the node, plus the spec:

| Field | Meaning |
|-------|---------|
| `gpu_type` | Required. Short or full name — `H100`, `RTX 4090`, `NVIDIA H200`. Matches a run of name tokens, so `H100` takes every H100 variant and `H20` does not take an H200 |
| `gpu_count` | GPUs to rent (default `1`): the whole node, or a split the provider allows |
| `min_vram_gb`, `min_download_mbps`, `min_ports` | Floors on the host. A host that does not report the figure does not qualify. `min_vram_gb` is compared with the memory the card reports, in GiB: an "80 GB" H100 reports 79.6, so ask for 79 or less |
| `min_cpus`, `min_ram_gb`, `min_disk_gb` | Floors on what the pod gets: a split rental's share of the host's CPUs, RAM (less the host's reserve, the larger of 4 GiB and 1 % of its RAM; at least 1 GiB) and free disk, in proportion to its GPUs |
| `max_price_per_gpu_hour` | Ceiling on the per-GPU-hour price, checked on the catalog and again on the live node right before the rent |
| `country` | ISO country code |
| `docker_in_docker` | Only nodes that run Docker-in-Docker (Sysbox) |
| `template_id` | Optional here: when omitted, the official template of the image recommended for the selected node's GPU and driver is used |
| `dry_run` | `true` selects and prices but rents nothing — what a client shows before asking to confirm |

Among the nodes that satisfy every constraint the API rents the **cheapest per GPU-hour**; ties go to faster ingress, then higher reliability score (a node without a score ranks at the median score of the matching nodes), then node id, so the same catalog always yields the same pick. If the chosen node is taken between selection and rent (400/409 from the rent path), the next candidate is tried, up to three in all. When all three are gone the answer is the last node's own 400 or 409, its message starting `Every matching node was taken before it could be rented`.

The response:

```json
{
  "success": true,
  "dry_run": true,
  "pod_id": null,
  "template_id": "<template_uuid>",
  "price_per_hour": 1.2,
  "selected_executor": { "id": "<executor_uuid>", "machine_name": "NVIDIA H100 80GB HBM3", "gpu_count": 8, "price_per_gpu": 1.2, "...": "the node as GET /nodes lists it" },
  "candidates": 12,
  "alternatives_considered": [ { "id": "…", "machine_name": "…", "price_per_gpu": 1.25, "price_per_hour": 1.25, "download_mbps": 1800, "country_code": "US" } ],
  "attempts": 0
}
```

`pod_id` is set on a real run (`dry_run: false`) — continue at Step 5 with it. `price_per_hour` is what the pod is charged: the node's live per-GPU price × `gpu_count` (a dry run quotes the catalog price, which can lag the live one by up to 30 s); `alternatives_considered` lists up to five of the other candidates, cheapest first, including any that were tried before the one rented. `attempts` is 0 on a dry run; above 1 means an earlier pick was taken and the next candidate was rented.

When nothing matches, the answer is **409** and names the constraint that emptied the list:

```json
{"success": false, "code": "no_executor_matches_spec", "constraint": "min_cpus", "candidates_before": 12,
 "message": "min_cpus=64: none of the 12 node(s) matching the earlier constraints satisfies it; the best on offer is 48 for 1 GPU(s)"}
```

The body also carries the standard `error` envelope (`code`, `message`, `hint`, `request_id`) every API error has; the flat fields above are the ones to read.

An unknown `gpu_type` lists the types on offer; a catalog that is still warming answers 503 with `Retry-After`. Balance (403) and template (404) errors are the request's own and are not retried on another node. When `template_id` is omitted and a picked node has no recommended template, that node is skipped like a taken one; on a dry run the answer is 400 asking for a `template_id`. `template_id` together with `dockerfile_content` is still refused (422). Authentication and rate limits are those of the rent path. The `Idempotency-Key` header works here as it does on the rent path: send one (a UUID is fine) and a retry after a lost response, with the same key and the same body within 24 hours, returns the first call's answer (the same `pod_id`, with `Idempotent-Replayed: true`) instead of renting another node. A refusal the rent itself returned is replayed too, so retry it with a new key; a request refused before any rent starts (a 422 validation error or a 429 rate limit) keeps no record, and the same key can be sent again. A call that failed with a 5xx or lost its answer may still have rented, so its key answers 409 `idempotency_outcome_unknown` from then on: check `lium ps` and rent again only with a new key. `POST /executors/cluster/rent` takes the header the same way. Until `GET /version` lists `"rent_idempotency"` under `features`, the backend ignores the header here and a retry rents again. `GET /version` lists `"rent_by_spec"` under `features`, so a client can detect the call with one small request and fall back to the pick-by-id path on a backend that predates it.

### Step 5 — Wait for it, then connect

```bash
curl https://lium.io/api/pods/<pod_id> \
  -H "X-API-Key: $LIUM_API_KEY" \
  | jq '{status, ssh_connect_cmd}'
```

Poll until `status` is `RUNNING`. At that point `ssh_connect_cmd` carries the command to reach the pod.

### Step 6 — Terminate it

Once the pod is running you are billed per second at the node's hourly price, counted from the moment the pod was created, until you do this (a start that fails is never billed; the full rule is the [billing lifecycle table](/pod-users/billing#when-a-pod-is-charged)):

```bash
curl -X DELETE https://lium.io/api/pods/<pod_id> \
  -H "X-API-Key: $LIUM_API_KEY"
```

**End state**: You have rented, used and released a pod entirely over the API. The live OpenAPI reference is at [lium.io/documents](https://lium.io/documents).

### Next — the CLI

Install the CLI for scripted workflows:

```bash
curl -fsSL https://lium.io/install.sh | bash   # or `pip install lium.io`
lium --help
```

See the [CLI installation guide](./cli/installation) for platform notes, version pinning, and the Python package alternative.

## Next steps

- [API](./api) — every endpoint, payload, and response
- [SDK](./sdk) — typed Python client built on the same API
- [CLI installation and quickstart](./cli/quickstart)
- [AI Agents](./agents) — MCP, llms.txt, OpenAPI, and CLI in one map
