Lium CLI & SDK
Lium — decentralized GPU rental platform on Bittensor. Pods are Docker containers with root SSH access and direct GPU passthrough.
- GitHub: https://github.com/Datura-ai/lium
- PyPI: https://pypi.org/project/lium.io/
- Docs: https://docs.lium.io
- Dashboard: https://lium.io
Quick Install
Standalone binary — no Python or dependencies required:
curl -fsSL https://raw.githubusercontent.com/Datura-ai/lium/main/scripts/install.sh | bash
This auto-detects OS (Linux/macOS) and architecture, downloads the binary to ~/.lium/bin/lium, and adds it to PATH.
After install, authentication depends on whether the user has a Lium account:
- No account, and no mailbox you can read → fingerprint signup: one call to
POST /auth/signup, no email, no password, no confirmation link. This is the fully autonomous path — see "No Account Yet — Sign Up". - No account, but a human is there to click a confirmation link →
lium signup --email <their email>— same section. Do not send the user to the web signup form. - Has an account →
lium init(opens a browser) orlium init --no-browserfor headless/agent use — see "Authentication Setup for Agents".
curl -fsS -X POST https://lium.io/api/auth/signup \
-H 'Content-Type: application/json' -d '{}' # no account, no mailbox
lium signup --email ada@example.com # no account, human can confirm
lium init # existing account, browser
lium init --no-browser # existing account, headless
Verify setup:
lium balance # prints a balance -> auth works; prints an error -> it does not
Alternative Install (via pip/uv)
# Via uv (isolated env)
curl -LsSf https://astral.sh/uv/install.sh | sh
uv tool install lium.io
# Via pip
pip install lium.io
Agent-Specific: Non-Interactive Usage
CRITICAL: Many lium commands are interactive by default. As an agent, always pass all parameters explicitly to avoid interactive prompts.
No Account Yet — Sign Up
lium init authenticates a user who already has an account. To create one, ask a single
question — can anyone read the mailbox? No: fingerprint signup, one call, fully autonomous.
Yes: lium signup --email <their real email>.
Only create an account when the user asks for one. Never invent an email address and never use a disposable inbox to fake the email path — when there is no mailbox, sign up with a fingerprint.
Fingerprint Signup — No Email, Fully Autonomous
A fingerprint here is a 32-character random string Lium mints as the account credential — not a passkey, not WebAuthn, not biometrics, not a device fingerprint. Nothing is scanned. One HTTP call completes the signup, so "I cannot sign up without a human" is wrong.
# empty body — Lium names the account itself (lium_a1b2c3)
curl -fsS -X POST https://lium.io/api/auth/signup \
-H 'Content-Type: application/json' -d '{}'
# {"user_id":"...","username":"lium_a1b2c3","fingerprint":"<32 chars>",
# "api_key":"sk_...","signup_credit_granted":true|false}
# when api_key is a string, store it and continue — no confirmation step in between
lium config set api.api_key sk_...
lium ls
api_keycan benull— minting it is allowed to fail without failing the signup. When it isnull, stop before configuring the CLI: sign in with the fingerprint and mint a key on the dashboard.fingerprintis the dashboard login at https://lium.io/login and the only recovery path — Lium stores just a hash. It is returned exactly once and nobody, support included, can look it up. Hand it to the user for their password manager, and treat it like a password: never paste it into a pod, a log, a commit or a ticket. Workloads use the API key instead.signup_credit_grantedsays whether the credit landed — do not promise $5 before reading it. It isfalsewhen the platform has the credit off, and when this IP already signed up once.
Send {}. The optional username is a display name only — it is not part of the login, the
web form never asks for one, and requesting a taken name fails with 409. Rate limit per IP:
5/min, 20/day. There is no CLI flag yet — lium signup is the email path only. Full walkthrough:
https://docs.lium.io/pod-users/fingerprint-signup
Email Signup — lium signup
When a human can confirm the address, lium signup does the whole cold start — no browser, no
dashboard, no web form:
lium signup --email ada@example.com # creates the account, stores the API key
lium ls # browse machines
lium up <node-id> -y # rent
lium ssh <pod> # connect
Ask for their real email first — the account, its balance and password recovery are tied to it, and the confirmation link is sent there.
lium signup prints the generated password — hand it to the user, it is their dashboard login
(to choose one instead, pass --password or set LIUM_SIGNUP_PASSWORD, which keeps it off argv).
Even when the command fails after the account was created — a timeout, or the API key could not
be read back — the error still reports the email and password, so the account is never stranded.
Add --json for a machine-readable
{email, password, api_key, signup_credit_granted, ssh_key_configured, next_steps}.
The key is written to ~/.lium/config.ini, so the account is then indistinguishable from one
set up with lium init.
Older binaries do not have the command. Probe for it, and update when it is missing:
lium --version # diagnostics only — probe the command itself below
lium signup --help >/dev/null 2>&1 || echo "CLI too old — update it"
# Binary install (installed via install.sh): auto-updates on launch, or force it
curl -fsSL https://lium.io/install.sh | bash
# pip / uv install
uv tool upgrade lium.io # or: pip install -U lium.io
On an older CLI that cannot be updated, the same signup is three HTTP calls:
BASE=https://lium.io/api
# No mailbox to confirm? Use the fingerprint signup above instead of steps 1-3.
# 1. Create the account. An API key named "Default" is minted server-side here; current
# backends return it in the response — {"msg": "success", "api_key": "sk_...",
# "signup_credit_granted": true|false} — older ones mint it without returning it.
curl -sX POST $BASE/users -H 'Content-Type: application/json' \
-d '{"name":"Ada","email":"ada@example.com","password":"..."}'
# 2-3. Only when the response had no api_key: log in for a JWT, read the key back (format sk_...).
TOKEN=$(curl -sX POST $BASE/users/login -H 'Content-Type: application/json' \
-d '{"email":"ada@example.com","password":"..."}' | jq -r .token)
KEY=$(curl -s $BASE/keys -H "Authorization: Bearer $TOKEN" \
| jq -r '.[] | select(.name=="Default") | .key')
lium config set api.api_key "$KEY"
# leave ssh.key_path unset — the CLI generates and configures the key on first use,
# and setting the path to a key that does not exist yet makes it skip that
Before the First Rental — Balance
lium up calls POST /executors/{executor_id}/rent, and the balance is the only gate: a fresh
account can rent as soon as it is funded, email confirmed or not. Map the error to the action:
| 403 on rent | Meaning | Action |
|---|---|---|
"Insufficient balance" |
The account balance is zero. | Fund the account — see "Funding options" below. |
Email confirmation
Confirming the address does not gate renting. It confirms the address itself, so that password
resets and account emails reach the user. Registration sends two mails: "Welcome to Celium!"
(no link in it) and "Please confirm your email" — only the second one carries the link. Point
the user at that subject, and ask them to click the link. That is the normal path.
Two endpoints on https://lium.io/api cover the cases where it does not work. Neither needs
auth; both take JSON:
# Mail never arrived / link expired (tokens are valid 24h) — send a fresh one
curl -sX POST https://lium.io/api/auth/resend-verify-email \
-H 'Content-Type: application/json' -d '{"email":"ada@example.com"}'
# 400 "User doesn't exist." or "Email is already verified." when it does not apply
# User pastes the link instead of clicking it — finish verification from its ?token=
curl -sX POST https://lium.io/api/auth/verify-email \
-H 'Content-Type: application/json' -d '{"token":"<token from the link>"}'
The $5 signup credit
The signup credit is $5 when the platform has it enabled and no other account has signed up
from this IP address — nothing about the email domain matters. Do not explain a 403 "Insufficient balance" with the credit: that error only says the balance is zero, and the answer to it is to
fund the account.
Whether it landed is answered by signup_credit_granted in the signup response (also in
lium signup --json): true → granted, false → not granted. When it is null or absent —
the backend does not report it — read the balance:
lium balance --json # {"balance_usd": 5.0}
Funding options
Dashboard: https://lium.io/billing
Headless invoice:
POST /tmc-pay/create-invoicewith headerX-API-Key: sk_...and a body of{"amount": <USD>, "crypto_currency": "...", "crypto_network": "..."}(all three required). Valid currency/network pairs come fromGET /tmc-pay/currencies(same API key header). The response carriesdeposit_address,crypto_amount,hosted_invoice_urlandexpires_at— give these to the user to pay from their wallet, do not move funds on their behalf.# {"currencies": [{"code": "USDT", "network": "tron", ...}, ...]} — pick a pair from here curl -s https://lium.io/api/tmc-pay/currencies -H "X-API-Key: sk_..." curl -sX POST https://lium.io/api/tmc-pay/create-invoice -H "X-API-Key: sk_..." \ -H 'Content-Type: application/json' \ -d '{"amount": 20, "crypto_currency": "USDT", "crypto_network": "tron"}'lium fund -w default -a 10.0 -yfor users with a Bittensor wallet — here-ais an amount of TAO, not dollars.-ameans USD only on the--alphapath, which moves Subnet-51 alpha the user already has staked:lium fund --alpha -k <hotkey-ss58> -a 10 -y.
SSH keys need no extra registration — the public key at ssh.key_path is registered
server-side right before renting.
Authentication Setup for Agents
For a user who already has an account (skip if you just ran the signup flow above and stored the key).
Preferred: two-step headless auth — no API key needed, no blocking, no browser:
- Run
lium init --no-browser— get auth URL and session ID (exits immediately) - Show the URL to the user, ask them to open it and click Approve
- Wait for user to confirm they approved
- Run
lium init --session <SESSION_ID>— saves API key + sets up SSH
lium init --no-browser
# [i] Open this URL to authenticate:
# https://lium.io/cli/approve/xJinnT3Vt6...
# [i] Then complete authentication with:
# lium init --session abc123def456
# ... user confirms they approved ...
lium init --session abc123def456
# [✓] API key saved
Fallback options (if --no-browser is unavailable or user already has an API key):
# Option 1: Direct config
lium config set api.api_key YOUR_API_KEY
lium config set ssh.key_path ~/.ssh/id_ed25519
# Option 2: Environment variable (session only)
export LIUM_API_KEY=YOUR_API_KEY
For fallback options, the user must get an API key from https://lium.io Account Settings.
Verify Setup
lium config show # check stored config
lium balance # prints a balance -> auth works
Non-Interactive Pod Creation
Always use -y flag and pass all parameters. Add --no-ssh too: without it a
successful lium up ends by opening an interactive SSH session, which stalls an
agent (--image mode streams container logs instead).
# WRONG (interactive):
lium up # one confirmation prompt before renting
lium up 1 # same — the prompt is the acquire confirmation
# RIGHT (non-interactive):
lium up --gpu H100 -y --no-ssh # auto-selects node + default template
lium up --gpu A100 -c 2 --country US -y --no-ssh # with filters
lium up --gpu H100 --name my-pod --ttl 6h -y --no-ssh # with name and auto-termination
lium up --gpu A6000 --image pytorch/pytorch:2.0 -y # custom docker image (streams logs)
lium up --gpu H100 --jupyter -y --no-ssh # with Jupyter
Rent by spec, not by id: lium up --gpu <type> [-c N] [--country CC] (and, coming with lium#209 — not in 0.0.37, the latest release — Lium.rent(gpu_type=, gpu_count=, min_cpus=, min_vram_gb=, max_price_per_gpu_hour=, …) in the SDK) picks a matching node and rents it in one call. The backend route that picks the cheapest is live (GET /version lists rent_by_spec); the released client 0.0.37 does not call it yet and picks locally (the first Pareto-optimal row of ls(), not the cheapest) — lium#209 makes lium up --gpu and Lium.rent use it. Do not lium ls first and rent the first row yourself: it is neither the cheapest nor guaranteed still free. On the 0.0.37 SDK, which has no rent(), ls() + up(executor_id=) is the only path — pick by price_per_hour, not the first row (example in references/sdk-reference.md).
Before You Rent 8 GPUs — Check Interconnect and Ingress First
A multi-GPU listing does not tell you how the GPUs are wired together or how fast the node pulls from the internet. Both decide whether a tensor-parallel / FSDP job runs at all and how long the weights take to arrive. Check them in the first minute of the rental, before any download or launch, and delete the pod if they are wrong.
lium exec <pod> "nvidia-smi topo -m; nvidia-smi topo -p2p r"
Read topo -m: every off-diagonal GPU cell must be NV# (e.g. NV18 on H100/H200,
NV12 on A100) for a proper HGX board. PIX/PXB/PHB/NODE/SYS means PCIe — several
times slower for NCCL collectives. Read topo -p2p r: every off-diagonal cell must be
OK; NS everywhere means peer-to-peer is disabled (seen on virtualised 8× H200 hosts),
and NCCL fails on its first all-reduce with unhandled cuda error / operation not supported.
Decision rule for TP/FSDP jobs: all NV# and all OK → proceed. Anything else →
lium rm <pod> -y and pick another node. If the job must run there anyway, NCCL only works
over loopback sockets, and TP=8 serving of a large model is impractical. Each lium exec is
a fresh SSH session, so a bare export in one call is gone in the next; pass the variables
with -e on the call that runs the job:
lium exec <pod> -e NCCL_P2P_DISABLE=1 -e NCCL_SHM_DISABLE=1 -e NCCL_IB_DISABLE=1 -e NCCL_SOCKET_IFNAME=lo "python train.py" # virtualised host without RDMA NICs
One independent process per GPU (batch inference, best-of-N generation, sweeps) does not need the interconnect and runs at full speed on any eight cards.
Ingress: the Download (Mbps) column in lium ls is a smoothed average of the validator's
VerifyX check, which fetches a real object of known size and hash (the speed-test average is
the fallback when VerifyX has no figure; Upload follows the same order). It flags nodes under
100 Mbps as slow; it does not predict Hugging Face or PyPI throughput. The same
756 GB checkpoint pulled at 2.6–4 GB/s, 1.04 GB/s and 45–200 MB/s on three nodes listed in
the same few-hundred-Mbps band. Measure before committing:
lium exec <pod> "curl -o /dev/null -sS -w '%{http_code} %{speed_download}\n' 'https://speed.cloudflare.com/__down?bytes=50000000'" # HTTP code, bytes/s (>= 100 MB is refused with 403)
lium exec <pod> "curl -L -o /dev/null -sS --max-time 20 -w '%{http_code} %{speed_download}\n' https://huggingface.co/openai-community/gpt2/resolve/main/model.safetensors" # same for the Hugging Face CDN; -L follows the redirect, 20 s cap, nothing written to disk
Do the arithmetic: bytes to download ÷ measured bytes/s. At 45 MB/s a 750 GB checkpoint is
4.6 h of idle GPU billing; at 1 GB/s it is 12.5 min. Uplink varies as much (30 KB/s vs
0.5 MB/s seen) — push results from the pod to Hugging Face / S3 directly rather than through
the controlling machine. An interconnect field, a CDN-measured ingress/egress figure and
--nvlink / --min-ingress filters are being added to the API and lium ls; until your
CLI shows a Link column, these commands are the check.
Non-Interactive Funding
# WRONG (interactive):
lium fund
# RIGHT:
lium fund -w default -a 10.0 -y # fund 10 TAO, skip confirmation
User must have a verified Bittensor wallet at https://lium.io/billing.
Agent Gotchas / Known Pitfalls
After Install — Export PATH
export PATH="$HOME/.lium/bin:$PATH" # needed in current shell session
After lium init --session — Verify with lium balance
After completing the two-step auth, run lium balance to verify: a balance means the key
works; an error with exit 3 means it does not. lium ls returns results with any key
(the node list is public), so it proves nothing about the key.
Exit Codes Hold Since 0.0.31 — Still Read the Output
Since lium 0.0.31 every command exits non-zero when it fails (table in
references/cli-commands.md § Exit Codes): lium ps with a revoked key exits 3,
a 403 exits 6, and lium ssh no-such-pod-xyz exits 5:
lium ssh no-such-pod-xyz # prints "No active pods" (or "Pod '…' not found"), exits 5
lium rm pod-a,pod-b -y # one refused by the API: "Removed 1 pod(s): pod-a" / "Failed to remove pods: pod-b", exits 1
Two things $? alone does not tell you: a batch (rm, reboot, scp, rsync)
finishes the batch and exits 1 naming the items that failed — read the line to
learn which — and an empty result is a success (lium ls --format json prints []
and exits 0 when no node matches). Prefer the machine-readable modes
(lium ls --format json, lium ps --format json, lium exec --json) and check the
result there: --json commands put a failure on stderr as one JSON object,
{"ok": false, "error": {...}}, with stdout empty; --format json prints
Error: ... as text. Releases before 0.0.31 printed Error: ... and exited 0 on
most failures (DAH-2593); pin lium>=0.0.31 when a script branches on $?.
Pod Targeting — Prefer Names
Use the pod name (e.g. lunar-lion-4c) from lium ps output for targeting — not a numeric index; indices shift with every listing.
-y Exists on the Destructive Commands
lium rm, lium up, lium fund, lium volumes rm, lium bk set/rm/restore all
take -y, --yes. No piped yes is needed:
lium rm my-pod # will prompt for confirmation
lium rm my-pod -y # non-interactive
lium rm -a -y # remove all pods non-interactively
Templates
- Without
--template_idor--image,lium upuses default PyTorch (CUDA) template — fastest to start - Default Docker-in-Docker (dind) template image:
daturaai/dind - Search templates:
lium templates pytorch(text search, no --format json) - To use specific template:
lium up --gpu H100 -t <TEMPLATE_ID> -y - To use custom Docker image:
lium up --gpu H100 --image pytorch/pytorch:2.0 -y
No User Identity Command
lium CLI has no renter-side identity command — no whoami for your API key.
(lium provider portal whoami exists, but it reports the provider portal session,
not the API key you rent with.)
To check the key, run lium balance: it prints a balance when the key works and,
since 0.0.31, exits 3 with an error when the key is bad or revoked. Do not use
lium ls for this: the node list is public, so it succeeds (exit 0) with any key.
Releases before 0.0.31 exited 0 on most failures — pin lium>=0.0.31 when a script
branches on $?.
Long-Running Commands Over SSH
lium exec runs commands in the foreground over SSH. Commands longer than ~30-60s (e.g. pip install vllm, huggingface-cli download) may be killed by SSH drop. Wrap with nohup + log redirect and poll the log:
# Start long command in background, detached from SSH session
# (the \$ escapes for the local shell; the remote sees literal $! which expands to the backgrounded bash PID)
lium exec my-pod "nohup bash -c 'pip install vllm' </dev/null >/tmp/install.log 2>&1 & echo PID=\$!"
# Watch progress
lium exec my-pod "tail -f /tmp/install.log"
# or stream via the logs endpoint if the command writes to stdout of PID 1
lium logs my-pod --follow
For fully-detached execution (survives SSH session close, stays running after lium exec returns):
lium exec my-pod "setsid nohup <cmd> </dev/null >/tmp/out.log 2>&1 &"
PEP 668 on Default PyTorch Template
The default daturaai/pytorch image is based on Ubuntu 24.04 where system pip is PEP 668 protected (externally-managed-environment). Use one of:
# Option 1: allow system-wide install
pip install --break-system-packages <pkg>
# Option 2: venv (recommended for isolation)
python -m venv /opt/env && source /opt/env/bin/activate && pip install <pkg>
# Option 3: uv (fast, handles isolation automatically)
curl -LsSf https://astral.sh/uv/install.sh | sh
uv pip install --system <pkg>
Missing System Libraries in Base Image
The default GPU base image does not include: jq, htop, tmux, screen, libnuma1, git-lfs, rsync. If your workload needs them:
lium exec my-pod "apt-get update && apt-get install -y libnuma1 jq tmux git-lfs"
Note: libnuma1 is required by sglang's sgl_kernel and some vllm configs — missing it causes cryptic "kernel not found" errors that actually mean the .so failed to load.
Cold-Start Expectations
Don't assume a pod is broken if it's quiet for several minutes after launch. Typical timings:
- Pod provisioning + SSH ready: ~30-60s
- Docker image pull: usually cached, ~0-30s
- Package installs (
pip install vllm): ~2-5 min - Model download from HuggingFace (4B-class): ~1-2 min; (70B+): ~5-10 min
- vLLM engine init (4B model, single GPU): ~2-3 min
- sglang + 70B+ sharded (CUDA graph capture of ~50 graphs): 15-25 min
Use lium logs my-pod --follow to watch progress, or poll a log file from lium exec.
Verify HuggingFace Model Exists Before Deploy
Before spinning up a pod for a specific model (e.g. vllm serve <repo>), confirm the repo_id exists on HuggingFace — typos like qwen3.5-4b (doesn't exist) vs Qwen/Qwen3-4B waste a full cold-start cycle.
curl -s "https://huggingface.co/api/models?search=qwen+2.5+7b&limit=10" | jq -r '.[].id'
Pod Vanishes from lium ps
Pods with internal status DELETING are filtered out of lium ps. FAILED pods remain visible (with FAILED status) — so if a pod was RUNNING and fully disappears, it's being deleted, not failing. To investigate:
- Check the dashboard (https://lium.io) — it shows full history including deleted pods
- Grab logs before the pod vanishes:
lium logs <name>(while it still exists) - Known issue: the CLI does not currently surface a deletion reason. If reproducible, report to the platform team.
Pod Creation Failures — 3-Minute Visibility Window
When lium up fails during provisioning, the pod is kept in status CREATION_FAILED for ~3 minutes before being auto-cleaned up (with a 10-min safety net if the cleanup task is delayed). During this window:
lium pswill show the pod with statusCREATION_FAILEDlium logs <name>may have partial output from the failed creation- After ~3 minutes the pod disappears — if your agent polled later, it will see no trace
For reliable failure diagnosis, poll lium ps every ~10-30s for the first few minutes after lium up, or check both RUNNING and terminal failure states explicitly.
"Executor Not Found" on lium up <id>
If an executor is visible on the lium.io dashboard but lium up <node-id> or lium ls doesn't show it, the platform's availability filter rejected it. Reasons include: low free disk space, high disk utilization, unresponsive health checks, or missing verification. lium ls is the source of truth for rentable machines — prefer filtering/selecting from lium ls output rather than matching IDs from the website.
CLI Quick Reference
Discovery
lium ls # all available GPUs (shows table with ★ for best price/perf)
lium ls --gpu H100 # filter by GPU type (there is no positional argument)
lium ls --sort download # sort by download speed (fastest first) — preferred default
lium ls --sort upload # sort by upload speed
lium ls --sort price_gpu # sort by price per GPU/hour
lium ls --format json # machine-parseable output
lium templates # list Docker templates
lium templates pytorch # search templates
Recommendation: When selecting machines for the user, prefer --sort download to get the fastest network unless the user specifically asks to sort by price or other criteria.
Pod Lifecycle
lium up --gpu H100 -y # create pod
lium ps # list active pods
lium ps --format json # machine-readable pod list
lium ssh my-pod # SSH into pod
lium exec my-pod "nvidia-smi" # run command
lium exec all "pip install torch" # batch exec on all pods
lium rm my-pod -y # stop pod
lium rm -a -y # stop all pods
Streaming Pod Logs
lium logs my-pod # snapshot of current stdout/stderr
lium logs my-pod --follow # stream logs live (Ctrl-C to stop)
Streams the Docker container's PID 1 stdout/stderr from the executor. Works for both image-mode and SSH-mode pods. Caveats:
- Right after
lium up, the endpoint may return 404 ("Pod container not deployed yet") for a few seconds — retry. - For SSH-mode pods, processes you start manually via
lium execare NOT PID 1, so their output won't appear here unless you redirect to/proc/1/fd/1(e.g.my_server > /proc/1/fd/1 2>&1) or tail your log files vialium exec my-pod "tail -f /tmp/out.log".
File Transfer
lium scp my-pod ./train.py # upload to /root/
lium scp my-pod ./data.csv /root/data/ # specific path
lium scp all ./config.json # upload to all pods
lium rsync my-pod ./project # sync directory
Pod Targeting
Pods accept: name, index from lium ps, comma-separated (1,2,3), or all.
Output Formats
Always use --format json when parsing output programmatically:
lium ls --format json | python -c "import json,sys; print(json.load(sys.stdin))"
lium ps --format json | python -c "import json,sys; print(json.load(sys.stdin))"
--format [table|json] exists on lium ls and lium ps. --json — a plain flag,
not a format choice — is taken by lium exec, lium fund, lium balance,
lium signup, lium topup create, lium topup currencies, and by the whole
lium provider group (set it on the group: lium provider --json node list).
lium templates has neither, and neither does anything else.
End-to-End Agent Workflow
Complete flow for setting up and renting a GPU pod:
# 1. Install lium (if not present)
if ! command -v lium >/dev/null 2>&1; then
curl -fsSL https://raw.githubusercontent.com/Datura-ai/lium/main/scripts/install.sh | bash
export PATH="$HOME/.lium/bin:$PATH"
fi
# 2a. No account, no mailbox → fingerprint signup, no human needed. Save `fingerprint` for the
# user (dashboard login, shown once). If api_key is null, mint one after signing in with it.
curl -fsS -X POST https://lium.io/api/auth/signup -H 'Content-Type: application/json' -d '{}'
lium config set api.api_key <API_KEY_FROM_RESPONSE>
# 2b. No account, but a human can click the confirmation link → ask for their real email
lium signup --email <USER_EMAIL>
# 2c. Existing account → two-step headless auth instead
lium init --no-browser
# → parse URL and session ID from output, show URL to user
# → wait for user to confirm they approved
lium init --session <SESSION_ID>
# 3. Verify the key (lium ls lists public data and succeeds with any key; a bad key exits 3 here since 0.0.31)
lium balance --json
# A zero balance means signup credit did not land; fund the account before renting.
# 4. Find suitable GPU (sort by speed by default)
lium ls --gpu H100 --sort download
# 5. Create pod (non-interactive! --no-ssh returns instead of opening a session)
lium up --gpu H100 --name work-pod --ttl 6h -y --no-ssh
# 6. Wait and verify (read the output, not just the exit code)
lium ps --format json
# 7. Use the pod
lium scp work-pod ./code.py
lium exec work-pod "python /root/code.py"
# 8. Cleanup
lium rm work-pod -y
Detailed References
- Full CLI command reference: references/cli-commands.md (also at https://raw.githubusercontent.com/Datura-ai/lium-skill/main/lium/references/cli-commands.md) — all commands, flags, volumes, backups, scheduling, port-forward, etc.
- Python SDK reference: references/sdk-reference.md (also at https://raw.githubusercontent.com/Datura-ai/lium-skill/main/lium/references/sdk-reference.md) — programmatic access via
lium.sdk.Lium,lium.Client,@machinedecorator, async patterns.