Skip to main content

Lium

from lium.sdk import Lium

Defined in lium.sdk.client.

Clean Unix-style SDK for Lium.

Lium(
config: Optional[lium.sdk.config.Config] = None,
source: str = 'sdk',
workspace: Optional[str] = None
)

Attributes​

NameTypeDescription
config
source
headers
api_keys
CLUSTER_IMAGE_PREFIX
JOB_ENV_VAR
GPU_QUERY_FIELDS
GPU_QUERY_COMMAND
TERMINAL_POD_STATUSES
MISSING_GRACE_SECONDS
FIRST_POLL_SECONDS
FIRST_POLL_WINDOW_SECONDS
FAST_POLL_SECONDS
FAST_POLL_WINDOW_SECONDS
SLOW_POLL_SECONDS
TRANSFER_KEY_ENV
TRANSFER_KEY_LOCK

Methods​

MethodDescription
featuresOptional API capabilities the backend advertises on GET /version.
supportsWhether the backend advertises feature (see features()).
list_ssh_keysReturn SSH keys registered for the current user.
register_ssh_keyRegister a new SSH public key under the current user.
default_ssh_key_namecli-<user>@<host> sanitised to A-Za-z0-9.@-.
upStart a new pod on a specific node.
pod_by_nameThe pod called name — or whose huid or id is name — if it exists.
rentalRent a pod on executorid for the duration of a with block and always remove it.
rentRent the cheapest available node that satisfies a spec, without listing the fleet.
podRetrieve detailed information about a specific pod.
logsStream logs from a pod.
editEdit a pod's template configuration.
lsList available nodes.
psList active pods.
billing_statementThe account's pod charges grouped by pod (GET /billing/statement), most recently billed first.
downStop a pod.
rmRemove pod (alias for down()).
rebootReboot a pod.
clustersGroups of free nodes that sit on one RDMA fabric and can be rented as one cluster.
cluster_offerThe offer for one fabric (exact id, or a unique prefix of it).
cluster_templateThe template every cluster rental must use (daturaai/lium-cluster); newest tag wins.
up_clusterRent several nodes of one fabric as a single all-or-nothing cluster.
my_clustersThe caller's cluster rentals, one Cluster per clusterid found in ps().
clusterOne cluster by id (exact, or a unique prefix), from the caller's pods.
wait_cluster_readyPoll until every member of cluster is RUNNING with SSH metadata.
rm_clusterRemove every member pod of a cluster with one DELETE /clusters/{clusterid}.
get_default_imagesGet default images for GPU type and driver version.
default_docker_templateResolve the best default template for a node ID.
templatesList available templates.
get_executorResolve a node by UUID or HUID against the same listing ls() returns.
ambiguous_executor_messageThe sentence for a HUID that is shared by several listed nodes.
executor_not_found_messageThe one sentence every caller prints when a node id resolves to nothing.
gpu_typesGet list of available GPU types.
gpu_short_typesThe short GPU types the marketplace knows (H100, RTX4090, ...), sorted.
unknown_gpu_typeNone when gpushort names a known GPU type; otherwise the list of known types.
request_machineAsk providers for a GPU type that is sold out (POST /machine-requests).
get_templateFetch a template by ID/HUID/name.
get_template_by_image_nameFetch a template by its Docker image + tag.
ssh_connectionSSH connection context manager.
ssh_sessionKeep one SSH connection to pod open for the whole block.
execExecute a shell command on a pod over SSH.
default_detach_log_path/workspace/logs/exec-<UTC stamp>-<6 hex>.log; the tail keeps two.
build_detached_commandThe remote command line that starts command in the background and prints its PID.
login_shell_env(prelude, execenv) that make a bash -lc job see env.
run_backgroundStart command in the background on a pod and return a Job to follow it.
jobRe-attach to a job started earlier with runbackground(), by name.
jobsEvery job that has a PID file under jobdir on the pod, running or finished.
wait_for_portBlock until TCP port accepts connections inside the pod (a server that is up).
parse_gpu_statsParse nvidia-smi --query-gpu=... --format=csv,noheader,nounits output.
gpu_statsPer-GPU utilization, memory, temperature and power on a pod, via nvidia-smi.
stream_execExecute a shell command and stream incremental output.
exec_allExecute a shell command on multiple pods in parallel.
poll_delaySeconds to sleep between two waitready polls.
pod_eventsThe pod's event log, oldest first — creation, reboots, failures with their error, and.
pod_failure_causeWhat the backend recorded as the reason the pod failed or was closed, or None.
wait_readyPoll until a pod reports RUNNING + SSH metadata.
scpUpload a local file to a pod via SFTP.
downloadDownload a file from a pod via SFTP.
uploadUpload a file to a pod via SFTP.
ssh_argvThe OpenSSH argument list that opens a shell on a pod.
refresh_podRe-read one pod from the API.
sshGet SSH command string for connecting to a pod.
rsync_optionsThe rsync flags shared by rsync() and cp().
rsyncSync files between the local machine and a pod with rsync.
cpCopy files from one pod to another over SSH, without passing through this machine.
transfer_keypairA fresh ed25519 key pair for one copy, as OpenSSH text.
grant_transfer_key_commandThe remote line that appends authorizedline to the pod's authorizedkeys.
revoke_transfer_key_commandThe remote line that drops every authorizedkeys entry carrying publickey.
switch_templateSwitch the template of a running pod.
create_templateCreate a new template.
wait_template_readyWait for template verification to complete.
meThe account the API key belongs to, as GET /users/me returns it.
get_my_user_idGet the current user's ID.
update_templateUpdate an existing template owned by the caller.
walletsGet the caller's configured funding wallets.
add_walletLink your wallet to the user account.
alpha_subnetsThe subnets whose alpha Lium accepts as payment (GET /balance/alpha/subnets).
convert_alphaQuote usd (USD) -> alpha via GET /balance/convert/alpha.
company_walletResolve the Lium deposit address via GET /wallet/company/?appid=.
backup_createCreate or replace a backup configuration for a pod.
backup_updateReplace the schedule of an existing backup configuration in place.
backup_nowTrigger an immediate backup for a pod.
backup_configReturn the backup configuration for a pod if one exists.
backup_listList all backup configurations across all pods.
backup_logsGet recent backup logs for a pod.
backup_logs_allGet all backup logs available to the current user.
backup_logGet one backup log owned by the authenticated user.
resolve_backup_idResolve an eight-character backup ID shown by the CLI.
backup_deleteDelete a backup configuration by ID.
backup_cancelRequest cancellation of an active backup while retaining its history.
backup_log_deleteDelete the stored data for a completed backup and retain its audit row.
restoreRestore a backup to a pod.
restore_logsGet recent restore logs for a pod.
resolve_restore_idResolve an eight-character restore ID shown by the CLI.
restore_cancelRequest cancellation of an active restore.
get_deployment_estimateEstimate deployment time for a template on a node.
balanceGet current account balance.
balance_or_noneThe balance from /users/me, or None when the answer has no balance field — where.
eventsThe account's event log, newest first (GET /users/me/events).
audit_logOne page of the account audit log, newest first (GET /account/audit).
topup_currenciesList stablecoin currencies/networks supported for self-serve top-ups.
topup_create_invoiceCreate a stablecoin top-up invoice for the current account.
topup_checkout_linkOpen a Stripe Checkout page that tops up this account by card (POST /stripe/create-checkout-session).
wait_for_creditPoll the balance until it rises above baseline (a top-up landed) or timeout seconds pass.
volumesList all volumes for the current user.
volumeGet a specific volume by ID.
volume_createCreate a new volume.
volume_updateUpdate a volume's metadata.
volume_deleteDelete a volume.
schedule_terminationSchedule a pod for automatic termination at a future date and time.
cap_spendSchedule the pod's removal for the moment it will have spent budgetusd.
cancel_scheduled_terminationCancel a scheduled termination for a pod.
install_jupyterInstall Jupyter Notebook on a pod.

features​

def features() -> set:

Optional API capabilities the backend advertises on GET /version.

Read once per client. A backend that predates the list, or one that cannot be reached, advertises nothing — callers then take their client-side path.

supports​

def supports(feature: str) -> bool:

Whether the backend advertises feature (see features()).

list_ssh_keys​

def list_ssh_keys() -> List[lium.sdk.models.SSHKey]:

Return SSH keys registered for the current user.

register_ssh_key​

def register_ssh_key(*, name: str, public_key: str) -> lium.sdk.models.SSHKey:

Register a new SSH public key under the current user.

default_ssh_key_name​

def default_ssh_key_name() -> str:

cli-<user>@<host> sanitised to [A-Za-z0-9._@-].

up​

def up(
*,
executor_id: str,
name: str = 'Your Pod',
template_id: Optional[str] = None,
image: Optional[str] = None,
dockerfile_content: Optional[str] = None,
volume_id: Optional[str] = None,
ports: Optional[int] = None,
ssh_keys: Optional[List[str]] = None,
ssh_name: Optional[str] = None,
enable_volume_encryption: bool | None = True,
backup_id: Optional[str] = None,
restore_path: Optional[str] = None,
gpu_count: Optional[int] = None,
wait: bool = False,
timeout: int = 600
) -> Union[Dict[str, Any], lium.sdk.models.PodInfo]:

Start a new pod on a specific node.

Arguments:

  • executor_id: Target node ID string.
  • name: Human-friendly pod name (defaults to "Your Pod").
  • template_id: Template ID. Defaults to the node's default template. Mutually exclusive with image and dockerfile_content.
  • image: Docker image to run, passed to the backend whole ("repo/name:tag", "registry:5000/team/img", "repo/name@sha256:<hex>"; the backend splits name, tag and digest and defaults the tag to latest), the same as lium up --image: a private one-time template is created for it with port 22 exposed and the image's own entrypoint/command, and deleted with the pod. Mutually exclusive with template_id and dockerfile_content.
  • dockerfile_content: Raw Dockerfile text to build the pod image from on the node (custom build). Mutually exclusive with template_id — pass exactly one. The image is built remotely with no network access, so the Dockerfile must be self-contained (no ADD <url> or ADD $\{var\} directives).
  • volume_id: Optional volume ID to attach on spawn.
  • ports: Number of exposed ports to request.
  • ssh_keys: SSH public keys to authorize. Defaults to the keys discovered by the Config.
  • ssh_name: Optional name to use when registering a new SSH key with the backend. Defaults to cli-<user>@<hostname>. Only applied to keys that are not already registered server-side.
  • enable_volume_encryption: Whether to request encryption for the local pod volume. Enabled by default. The image must support Lium volume encryption.
  • backup_id: Optional backup ID to restore after the pod starts.
  • restore_path: New or empty subdirectory where the backup is restored. Required when backup_id is provided.
  • wait: When True, block until the pod is RUNNING with an SSH endpoint and return it as a PodInfo. The pod is billing from the moment the rent call returns, so a timeout raises a LiumError that names the pod id.
  • timeout: Seconds to wait for readiness when wait is set.
  • gpu_count: Rent only this many of the node's GPUs (GPU splitting). None takes every GPU that is free on the node right now (the whole node when none of it is rented). The API rejects a count above the free GPUs, below the provider's minimum, or on nodes that do not allow splitting.

Returns: Pod metadata as returned by the rent API (id, name, status, ssh command, etc.), or the ready PodInfo when wait is True.

pod_by_name​

def pod_by_name(name: str) -> Optional[lium.sdk.models.PodInfo]:

The pod called name — or whose huid or id is name — if it exists.

Names are chosen by the caller and huids are what lium ps prints, so both are accepted; the first match wins when several pods share a name.

Arguments:

  • name: Pod name, huid, or id.

Returns: The matching PodInfo, or None.

rental​

def rental(
*,
executor_id: str,
timeout: int = 600,
**up_kwargs: Any
) -> Generator[lium.sdk.models.PodInfo, NoneType, NoneType]:

Rent a pod on executor_id for the duration of a with block and always remove it.

(rent() is the other way to rent: by spec, returning a RentResult the caller owns; rental is the scoped form that cleans up after itself.)

The pod is removed on the way out whether the block returned, raised, or was interrupted, and also when the pod was created but never became ready. A pod that outlives the code that needed it is the most common way to pay for nothing.

Arguments:

  • executor_id: Target node ID.
  • timeout: Seconds to wait for the pod to become ready.
  • **up_kwargs: Any other keyword argument up() accepts (name, template_id, ports ...).

Yields: The ready PodInfo.

Example:

with lium.rental(executor_id=node.id, name="job") as pod:

... lium.exec(pod, command="nvidia-smi")

rent​

def rent(
*,
gpu_type: str,
gpu_count: int = 1,
name: str = 'Your Pod',
template_id: Optional[str] = None,
dockerfile_content: Optional[str] = None,
min_vram_gb: Optional[float] = None,
min_cpus: Optional[int] = None,
min_ram_gb: Optional[float] = None,
min_disk_gb: Optional[float] = None,
min_download_mbps: Optional[float] = None,
min_ports: Optional[int] = None,
max_price_per_gpu_hour: Optional[float] = None,
country: Optional[str] = None,
docker_in_docker: Optional[bool] = None,
interconnect: Optional[str] = None,
volume_id: Optional[str] = None,
ports: Optional[int] = None,
ssh_keys: Optional[List[str]] = None,
ssh_name: Optional[str] = None,
enable_volume_encryption: bool | None = True,
backup_id: Optional[str] = None,
restore_path: Optional[str] = None,
dry_run: bool = False
) -> lium.sdk.models.RentResult:

Rent the cheapest available node that satisfies a spec, without listing the fleet.

When the backend advertises rent_by_spec (GET /version), one call to POST /executors/rent-by-spec selects and rents: the server picks the cheapest $/GPU·h node that meets every constraint (ties: faster ingress, then reliability, then id), rents it under its own locks and, if that node is taken meanwhile, tries the next candidate. Against an older backend the same arguments make today's client-side pick — list, filter, cheapest exact match — and rent it with up().

Arguments:

  • gpu_type: Short or full GPU name — "H100", "RTX4090", "NVIDIA H200".
  • gpu_count: GPUs to rent (default 1). Server-side this also admits a split of a larger node when its provider allows one; client-side it is the node's size.
  • name: Pod name.
  • template_id: Template to run. Omitted: the node's recommended image. Mutually exclusive with dockerfile_content.
  • dockerfile_content: Build the image from this Dockerfile instead (see up()).
  • min_vram_gb, min_cpus, min_ram_gb, min_disk_gb, min_download_mbps, min_ports: Floors on the host; a host that does not report the figure does not qualify.
  • max_price_per_gpu_hour: Ceiling on price_per_gpu.
  • country: ISO country code.
  • docker_in_docker: Require a sysbox host.
  • interconnect: "nvlink" — every GPU pair on NVLink (unreported counts as no). Client-side only: a rent-by-spec backend has no such field, so the call is refused there rather than sent with a constraint the server would not check.
  • volume_id, ports, ssh_keys, ssh_name, enable_volume_encryption, backup_id, restore_path: as in up().
  • dry_run: Choose and price only; nothing is rented and no SSH key is registered with the account. Against a rent-by-spec backend the request still carries your SSH public key (ssh_keys or the configured key: the server validates a dry run as a rent); the client-side pick needs no key.

Returns: RentResult — the node, the GPUs rented, the hourly price, the pod (None on a dry run), the template used, how many nodes qualified and the runners-up.

Raises:

  • LiumError: No node satisfies the spec. The message names the constraint that left no candidate and the best value on offer, e.g. min_cpus=64: none of the 12 node(s) matching the earlier constraints satisfies it; the best on offer is 48.
  • LiumError: interconnect was given and the backend rents by spec (nothing is registered or rented).
  • LiumServerError, requests.RequestException: the rent-by-spec response was lost. The rent is posted once (a repeat could rent a second node); the pod is looked up by name for ~9 s and returned when it appears, otherwise the error propagates and the pod may still exist — check ps().

pod​

def pod(pod_id: str) -> Dict[str, Any]:

Retrieve detailed information about a specific pod.

Arguments:

  • pod_id: The unique identifier of the pod to retrieve.

Returns: Raw pod data dictionary including template, node, status, and connection info.

logs​

def logs(
pod_id: str,
*,
tail: int = 100,
follow: bool = False
) -> Generator[bytes, NoneType, NoneType]:

Stream logs from a pod.

Arguments:

  • pod_id: The unique identifier of the pod.
  • tail: Number of lines to retrieve from the end of the logs (default: 100).
  • follow: If True, stream logs continuously (default: False).

Yields: Log lines as bytes.

edit​

def edit(pod_id: str, **kwargs) -> Dict[str, Any]:

Edit a pod's template configuration.

Updates the template associated with a pod by merging the provided keyword arguments with the existing template settings.

Arguments:

  • pod_id: The unique identifier of the pod whose template to edit.
  • **kwargs: Template fields to update. Common fields include:
    • docker_image (str): Docker image repository.
    • docker_image_tag (str): Docker image tag.
    • startup_commands (str): Commands to run on container start.
    • internal_ports (List[int]): Ports to expose.
    • environment (Dict[str, str]): Environment variables.
    • volumes (List[str]): Volume mount paths.

Returns: Updated template data dictionary from the API.

Example:

lium.edit(pod_id, startup_commands="python main.py", environment={"DEBUG": "1"})

ls​

def ls(
*,
gpu_type: Optional[str] = None,
gpu_count: Optional[int] = None,
lat: Optional[float] = None,
lon: Optional[float] = None,
max_distance_miles: Optional[int] = None,
min_cuda_version: Optional[float] = None,
min_cpus: Optional[int] = None,
nvlink: Optional[bool] = None,
min_download_mbps: Optional[float] = None,
view: str = 'summary'
) -> List[lium.sdk.models.ExecutorInfo]:

List available nodes.

Arguments:

  • gpu_type: Optional GPU filter such as "A100" or "H200".
  • gpu_count: Exact GPU count to match (defaults to 8, pass None to disable).
  • lat: Optional latitude for geospatial filtering. Must be used together with lon and max_distance_miles.
  • lon: Optional longitude for geospatial filtering. Must be used together with lat and max_distance_miles.
  • max_distance_miles: Optional radius (in miles) for geospatial filtering. Must be used together with lat and lon.
  • min_cuda_version: Optional minimum CUDA version to require (e.g. 12.4). Nodes whose max_cuda_version is None or below this threshold are excluded. NVIDIA drivers are backward compatible, so a node with a higher driver CUDA version satisfies the requirement.
  • min_cpus: Optional minimum CPU thread count (specs.cpu.count). Nodes that report fewer CPUs, or none, are excluded.
  • nvlink: True keeps only nodes where Lium's node checks saw every GPU pair on NVLink (nvlink). Nodes with no verdict yet are excluded — a renter who asks for NVLink must not be handed a PCIe box. False/None do not filter.
  • min_download_mbps: Minimum Download in Mbps, judged on effective_download_speed_mbps (the figure lium ls shows as Download). Nodes with no figure are excluded.
  • view: "summary" (default) asks the API for the fields a listing reads — price, GPU/CPU/RAM/disk headline specs, location, tier, network; an API that does not know the parameter returns the full row. "full" asks for the whole node-check scrape in specs (docker info, verified ports, per-GPU telemetry, checksums).

Returns: A list of ExecutorInfo objects that satisfy the filters.

ps​

def ps(*, api_key_id: Optional[str] = None) -> List[lium.sdk.models.PodInfo]:

List active pods.

Arguments:

  • api_key_id: Only the pods rented through this API key (GET /pods?api_key_id=…, server support pending). A server without the filter ignores the parameter and lists every pod the caller can see.

Returns: List of PodInfo objects representing the caller's running pods.

billing_statement​

def billing_statement(
*,
start_day: Optional[str] = None,
end_day: Optional[str] = None,
api_key_id: Optional[str] = None
) -> Dict[str, Any]:

The account's pod charges grouped by pod (GET /billing/statement), most recently billed first.

Every pod the account was charged for in the period — removed pods included — with days, one entry per ledger row, oldest first; a UTC day can have more than one row (for example when billing stops and resumes). Each pod's total is what the ledger debited. start_day / end_day are UTC billing days (YYYY-MM-DD), both inclusive and optional. api_key_id keeps only the pods rented through that key (the rows then carry api_key_name); a server without the filter ignores it. Returns the server's \{"start_day", "end_day", "total", "pods": [...]\}. A key needs the read scope.

down​

def down(pod: lium.sdk.models.PodInfo) -> Dict[str, Any]:

Stop a pod.

Arguments:

  • pod: Pod to terminate.

Returns: API response payload from the delete call.

rm​

def rm(pod: lium.sdk.models.PodInfo) -> Dict[str, Any]:

Remove pod (alias for down()).

Arguments:

  • pod: Pod to terminate.

Returns: API response payload from the delete call.

reboot​

def reboot(
pod: lium.sdk.models.PodInfo,
volume_id: Optional[str] = None
) -> Dict[str, Any]:

Reboot a pod.

Arguments:

  • pod: Pod to reboot.
  • volume_id: Optional volume ID to attach for the reboot request.

Returns: Pod data from the API response after issuing the reboot.

clusters​

def clusters() -> List[lium.sdk.models.ClusterOffer]:

Groups of free nodes that sit on one RDMA fabric and can be rented as one cluster.

GET /executors/infiniband-clusters. Each offer names the fabric, whether it is InfiniBand or RoCE, whether the wire between the members was measured, how many nodes the fabric has and which of them are free right now.

cluster_offer​

def cluster_offer(fabric_id: str) -> Optional[lium.sdk.models.ClusterOffer]:

The offer for one fabric (exact id, or a unique prefix of it).

cluster_template​

def cluster_template() -> lium.sdk.models.Template:

The template every cluster rental must use (daturaai/lium-cluster); newest tag wins.

up_cluster​

def up_cluster(
executor_ids: List[str],
*,
name: str,
template_id: Optional[str] = None,
ssh_keys: Optional[List[str]] = None,
ssh_name: Optional[str] = None,
ports: Optional[int] = None,
enable_volume_encryption: bool | None = True,
wait: bool = False,
timeout: int = 900
) -> lium.sdk.models.Cluster:

Rent several nodes of one fabric as a single all-or-nothing cluster.

POST /executors/cluster/rent. Every node is taken whole and runs the cluster template, which raises the private overlay each member uses to find its peers. The same name is given to every member pod; the members share cluster_id.

Arguments:

  • executor_ids: Nodes to rent; they must all be on one fabric (see clusters()).
  • name: Pod name applied to every member.
  • template_id: Defaults to cluster_template().
  • ssh_keys: Public keys to install; defaults to the configured key.
  • ports: Ports to expose per node.
  • enable_volume_encryption: As for up().
  • wait: Block until every member is RUNNING with SSH (timeout seconds).

Raises:

  • ValueError: fewer than two nodes, or no SSH key.
  • ClusterNotListedError: the nodes are, or may be, rented, but the listing did not show a whole cluster: the API confirmed the order and named the member pods but the listing did not show every one of them (or failed); or the order got no answer and the by-name lookup found fewer members than requested (or could not list). Do not rent again.
  • LiumError: the API refused the group (mixed fabrics, split hosts, wrong image); the pod listing before the order failed (nothing was rented); or the order got no answer and no member appeared.

my_clusters​

def my_clusters() -> List[lium.sdk.models.Cluster]:

The caller's cluster rentals, one Cluster per cluster_id found in ps().

cluster​

def cluster(cluster_id: str) -> lium.sdk.models.Cluster:

One cluster by id (exact, or a unique prefix), from the caller's pods.

Raises:

  • LiumNotFoundError: no pod carries that cluster id.

wait_cluster_ready​

def wait_cluster_ready(
cluster: lium.sdk.models.Cluster,
*,
timeout: int = 900,
poll_interval: int = 10
) -> lium.sdk.models.Cluster:

Poll until every member of cluster is RUNNING with SSH metadata.

The members waited for are the pods of cluster as given: one of them missing from any poll, the first included, is a member that vanished, not one that is slow.

Raises:

  • PodStartError: a member reached a terminal status (FAILED, STOPPED, …) or is missing from the pod list. Reported at once, not at the deadline, since the other members keep billing.
  • TimeoutError: some member was still not ready after timeout seconds (all members keep billing).

rm_cluster​

def rm_cluster(cluster: lium.sdk.models.Cluster) -> List[Dict[str, Any]]:

Remove every member pod of a cluster with one DELETE /clusters/\{cluster_id\}.

Returns one \{"pod", "huid", "name", "node_rank", "success", "message", "error"\} per member, in the order the server reports them (huid is the short name lium rm accepts). The server finds the members by the cluster id, checks ownership and API-key scope on every one of them before the first delete, then tears them down one by one; a member that failed is reported with success false and its error text while the others are still removed, so nothing is left billing by accident. Call again to retry the members that failed. The pinned ssh host key of every member the server accepted is dropped, as rm() does for a single pod.

There is no per-pod fallback: a 404 from the route removes nothing and is raised.

Raises:

  • LiumNotFoundError: no pod carries the id (the cluster is already gone), or the server has no DELETE /clusters/\{id\} route yet. Nothing was removed.
  • LiumPermissionError: a member belongs to another user, or the API key lacks the manage scope for a pod it did not create. Nothing was removed.
  • LiumError: 409 — a member is still being created and the platform is set not to cancel in-flight creates; nothing was removed, call again later. Also a 200 without the per-member rows, so an unexpected answer is never read as "removed".

get_default_images​

def get_default_images(gpu_model: Optional[str], driver_version: Optional[str]) -> list[dict]:

Get default images for GPU type and driver version.

default_docker_template​

def default_docker_template(executor_id: str) -> lium.sdk.models.Template:

Resolve the best default template for a node ID.

Arguments:

  • executor_id: Node identifier returned by ls().

Returns: Template best suited for the node.

Raises:

  • ValueError: If no matching node or template exists.

templates​

def templates(
filter: Optional[str] = None,
only_my: bool = False
) -> List[lium.sdk.models.Template]:

List available templates.

Arguments:

  • filter: Optional substring to filter by image or name.
  • only_my: When True return only templates owned by the caller.

Returns: List of Template.

get_executor​

def get_executor(executor: str) -> Optional[lium.sdk.models.ExecutorInfo]:

Resolve a node by UUID or HUID against the same listing ls() returns.

Arguments:

  • executor: Node UUID, or the HUID lium ls prints for it (cosmic-hawk-f2).

Returns: Matching ExecutorInfo or None if no listed node has that id.

Raises:

  • ValueError: the HUID names more than one listed node. HUIDs are client-side draws (10 adjectives × 10 nouns × 256 tails), so a full listing can hold two nodes with the same one; picking the first in API order would rent a different node than the one lium ls showed, so the caller is asked for the UUID instead.

ambiguous_executor_message​

def ambiguous_executor_message(executor: str, matches: List[lium.sdk.models.ExecutorInfo]) -> str:

The sentence for a HUID that is shared by several listed nodes.

executor_not_found_message​

def executor_not_found_message(executor: str) -> str:

The one sentence every caller prints when a node id resolves to nothing.

gpu_types​

def gpu_types() -> set[str]:

Get list of available GPU types.

Returns: Set of GPU type strings advertised by the API.

gpu_short_types​

def gpu_short_types() -> List[str]:

The short GPU types the marketplace knows (H100, RTX4090, ...), sorted.

These are the values --gpu / ls(gpu_type=) accept; a bare model number (4090) and spacing/case variants (rtx 4090) resolve to them too. Names the extractor could not type (it falls back to the last word: Ti, SUPER, V) are left out — they are not something --gpu can usefully take.

unknown_gpu_type​

def unknown_gpu_type(gpu_short: str) -> Optional[List[str]]:

None when gpu_short names a known GPU type; otherwise the list of known types.

Lets a caller tell "every 4090 is rented" from "nothing is called 4090" — the second case is what a typo or an unsupported spelling produces, and the two need different messages. Never raises: on an API failure the answer is None (assume known), so a listing failure is reported as such and not as a typo.

request_machine​

def request_machine(gpu: str, count: Optional[int] = None) -> Dict[str, Any]:

Ask providers for a GPU type that is sold out (POST /machine-requests).

gpu is a short type (B300, 4090) or a full catalog name. The providers who list that type are told, and the request shows in lium.io/machine-requests. A short type that covers several catalog names must be given by one of them.

Returns: The API's answer: provider_notified_count and, when present, confirmation.

get_template​

def get_template(template_id: str) -> Optional[lium.sdk.models.Template]:

Fetch a template by ID/HUID/name.

Arguments:

  • template_id: Template ID, HUID, or name to match.

Returns: Matching Template or None if not found.

get_template_by_image_name​

def get_template_by_image_name(
image_name: Optional[str] = None,
image_tag: Optional[str] = None
) -> Optional[lium.sdk.models.Template]:

Fetch a template by its Docker image + tag.

Arguments:

  • image_name: Repository/image name.
  • image_tag: Tag to match.

Returns: Matching Template or None if not found.

ssh_connection​

def ssh_connection(pod: lium.sdk.models.PodInfo, timeout: int = 30):

SSH connection context manager.

Arguments:

  • pod: Pod whose SSH metadata is used.
  • timeout: Connection timeout in seconds.

Yields: An active paramiko.SSHClient.

Raises:

  • ValueError: no ssh_cmd or no SSH key configured, or pod.ssh_cmd is not ssh <user>@<host> [-p <port>] (ssh_target(), the check the OpenSSH path applies). Nothing is connected then.

ssh_session​

def ssh_session(pod: lium.sdk.models.PodInfo, timeout: int = 30):

Keep one SSH connection to pod open for the whole block.

Every exec(), stream_exec(), upload() and download() inside it runs over this connection instead of paying a fresh TCP + SSH handshake each (several seconds per call to a distant node).

Yields: The active paramiko.SSHClient.

exec​

def exec(
pod: lium.sdk.models.PodInfo,
*,
command: str,
env: Optional[Dict[str, str]] = None,
timeout: Optional[float] = None,
detach: bool = False,
log_path: Optional[str] = None
) -> Dict[str, Any]:

Execute a shell command on a pod over SSH.

Arguments:

  • pod: Pod to target.
  • command: Shell command to run remotely.
  • env: Optional environment variables exported before the command runs. The values are sent over the session's stdin, not in the remote command line, so ps on the pod never shows them.
  • timeout: Seconds to wait for the command to finish. When it runs out the channel is closed and TimeoutError is raised; the remote process may keep running. With detach it bounds only the launcher (default 60 s), never the job.
  • detach: Start the command in the background on the pod and return at once. The command runs under nohup setsid with stdin closed and stdout/stderr to log_path, so it survives the SSH session ending — the shape every user otherwise rediscovers by hand. env still travels over stdin, and is applied inside the detached login shell after its profile, so the given value wins.
  • log_path: Log file for detach (default /workspace/logs/exec-<UTC timestamp>-<id>.log, the <id> a 6-hex tail that keeps two launches in the same second apart).

Returns: Dict containing stdout, stderr, exit_code, and success flag; with detach a dict with pid, log_path and command.

Raises:

  • ValueError: an env name is not a shell identifier. Raised before the connection is opened, so nothing reaches the pod.

default_detach_log_path​

def default_detach_log_path() -> str:

/workspace/logs/exec-<UTC stamp>-<6 hex>.log; the tail keeps two jobs started in the same second from sharing (and truncating) one file.

build_detached_command​

def build_detached_command(command: str, log_path: str) -> str:

The remote command line that starts command in the background and prints its PID.

nohup alone is not enough over a non-interactive SSH session: the child keeps the session's stdin and its process group, so exec blocks until the child exits, and a closed session can still take the child down. setsid gives it its own session, < /dev/null detaches stdin, and both output streams go to the log file. echo $! is the only thing the caller reads back on stdout.

login_shell_env​

def login_shell_env(
cls,
env: Optional[Dict[str, str]]
) -> Tuple[str, Optional[Dict[str, str]]]:

(prelude, exec_env) that make a bash -lc job see env.

prelude goes in front of the job's command inside the login shell; exec_env is what the launching exec() call exports over stdin. Both are empty when env is. lium exec --detach uses it the same way for the launcher it builds itself. An export the job shell cannot apply (a name bash keeps read-only, such as UID, or one the pod's profile marked readonly) ends the shell with exit 1 and the reason in the log; the command never runs with a different environment than the one asked for.

Raises:

  • ValueError: an env name is not a shell identifier, or is JOB_ENV_VAR itself (the carrier, unset before the command runs).

run_background​

def run_background(
pod: lium.sdk.models.PodInfo,
command: str,
*,
name: Optional[str] = None,
env: Optional[Dict[str, str]] = None,
workdir: Optional[str] = None,
job_dir: str = '/workspace/logs',
timeout: float = 60
) -> lium.sdk.jobs.Job:

Start command in the background on a pod and return a Job to follow it.

The job survives this SSH session and this process: it runs under nohup setsid with stdin closed, logs to <job_dir>/<name>.log, records its PID in <name>.pid (with the process's boot id and start time in <name>.id, so a reused PID never passes as the job; a job without that file is never trusted either) and its exit code in <name>.exit when it ends. job() re-attaches later by name.

Arguments:

  • pod: Pod to run on.
  • command: Shell command (run through bash -lc, so the login environment — conda, PATH — applies).
  • name: Job name, also the stem of its files; default job-<UTC timestamp>. Refused when a job of that name is still running on the pod.
  • env: Environment variables for the job. Sent over the session's stdin like exec() does, never in a command line, and applied inside the job shell after its login profile, so the given value wins.
  • workdir: Directory to cd into first.
  • job_dir: Where the job files live (default /workspace/logs, the fast local volume, not the encrypted /root).
  • timeout: Seconds allowed for the launcher itself (not the job).

Raises:

  • LiumError: the launcher printed no PID, or the name is taken by a live job.

job​

def job(
pod: lium.sdk.models.PodInfo,
name: str,
*,
job_dir: str = '/workspace/logs'
) -> lium.sdk.jobs.Job:

Re-attach to a job started earlier with run_background(), by name.

Raises:

  • LiumNotFoundError: no job of that name has files on the pod.

jobs​

def jobs(
pod: lium.sdk.models.PodInfo,
*,
job_dir: str = '/workspace/logs'
) -> List[lium.sdk.jobs.Job]:

Every job that has a PID file under job_dir on the pod, running or finished.

wait_for_port​

def wait_for_port(
pod: lium.sdk.models.PodInfo,
port: int,
*,
timeout: float = 600,
host: str = '127.0.0.1',
poll_interval: float = 3
) -> None:

Block until TCP port accepts connections inside the pod (a server that is up).

The probe runs on the pod over SSH (bash /dev/tcp, no nc needed), so it sees the port as the pod does. For a job started with run_background() prefer wait_for_port(), which also stops early when the job dies.

Raises:

  • TimeoutError: the port did not answer within timeout seconds.

parse_gpu_stats​

def parse_gpu_stats(csv_text: str) -> List[lium.sdk.models.GpuStats]:

Parse nvidia-smi --query-gpu=... --format=csv,noheader,nounits output.

[N/A] and [Not Supported] become None and the rest of the reading is kept; a GPU whose power sensor is missing still has a utilization figure worth showing.

gpu_stats​

def gpu_stats(
pod: lium.sdk.models.PodInfo,
*,
timeout: float = 30
) -> List[lium.sdk.models.GpuStats]:

Per-GPU utilization, memory, temperature and power on a pod, via nvidia-smi.

Raises:

  • LiumError: when nvidia-smi failed or printed nothing usable.

stream_exec​

def stream_exec(
pod: lium.sdk.models.PodInfo,
*,
command: str,
env: Optional[Dict[str, str]] = None,
pty: bool = True
) -> Generator[Dict[str, str], NoneType, int]:

Execute a shell command and stream incremental output.

Args: pod: Pod to target. command: Shell command to run remotely. env: Optional environment variables exported before the command runs. pty: Request a pseudo-terminal (default). A pty merges stderr into stdout and turns into ; pass False to keep the two streams apart, as lium.machine() does to relay a function's output.

Yields: Streaming output chunks as \{"type": "stdout"|"stderr", "data": str\}.

Returns: The command's exit status (the generator's StopIteration.value).

exec_all​

def exec_all(
pods: List[lium.sdk.models.PodInfo],
*,
command: str,
env: Optional[Dict[str, str]] = None,
max_workers: int = 10
) -> List[Dict]:

Execute a shell command on multiple pods in parallel.

Arguments:

  • pods: List of pods to target.
  • command: Shell command to run on each pod.
  • env: Optional environment variables exported before each command.
  • max_workers: Maximum number of SSH workers to spawn.

Returns: List of result dictionaries mirroring exec(), each with the pod id under "pod". When SSH fails for a pod its entry is \{"pod": <id>, "error": <message>, "success": False\} — same key, same type as the successful entries, so callers can index results by pod id without checking which shape they got.

Raises:

  • ValueError: an env name is not a shell identifier. Checked once, before any pod is contacted — a caller's mistake is not one failure entry per pod.

poll_delay​

def poll_delay(cls, elapsed: float, poll_interval: Optional[int] = None) -> float:

Seconds to sleep between two wait_ready polls.

A caller-given poll_interval is used as-is; None selects the adaptive schedule (FIRST_POLL_SECONDS for the first FIRST_POLL_WINDOW_SECONDS seconds, FAST_POLL_SECONDS until FAST_POLL_WINDOW_SECONDS, SLOW_POLL_SECONDS after that).

pod_events​

def pod_events(pod_id: str) -> List[Dict[str, Any]]:

The pod's event log, oldest first — creation, reboots, failures with their error, and lifecycle entries saying why it left RUNNING. Answers for a pod whose row is already gone.

Returns [] against a backend that predates the endpoint.

pod_failure_cause​

def pod_failure_cause(pod_id: str) -> Optional[str]:

What the backend recorded as the reason the pod failed or was closed, or None.

The latest event carrying an error (a failed create or reboot: the node's error headline) or a lifecycle reason/detail wins. Never raises — this is read on a failure path.

wait_ready​

def wait_ready(
pod: Union[str, lium.sdk.models.PodInfo, Dict],
*,
timeout: Optional[int] = 300,
poll_interval: Optional[int] = None,
on_poll: Optional[Callable[[Optional[lium.sdk.models.PodInfo], str, float], NoneType]] = None,
ready_port: Optional[int] = None
) -> Optional[lium.sdk.models.PodInfo]:

Poll until a pod reports RUNNING + SSH metadata.

Arguments:

  • pod: Pod identifier, PodInfo, or dict with an id field.
  • timeout: Maximum number of seconds to wait; None waits until the pod is ready or fails.
  • poll_interval: Fixed interval between successive ps calls; None (default) polls every FIRST_POLL_SECONDS for the first FIRST_POLL_WINDOW_SECONDS seconds, every FAST_POLL_SECONDS until FAST_POLL_WINDOW_SECONDS, then every SLOW_POLL_SECONDS — see poll_delay().
  • on_poll: Called after every poll with the pod as last listed (or None), its status ("missing" when not listed) and the seconds elapsed, so a caller can show progress while waiting.
  • ready_port: When given, also wait until this TCP port answers inside the pod (a template that serves a model on start is not usable when RUNNING, only when its port accepts). The same timeout bounds both phases together; with timeout=None the port wait uses wait_for_port()'s own default.

Returns: PodInfo when the pod is ready, otherwise None if the timeout expires while the pod is still starting (or, with ready_port, while the port has not answered yet).

Raises:

  • PodStartError: The pod reached a terminal status (FAILED, CREATION_FAILED, STOPPED, …), vanished from the pod list after being seen, or was still not listed MISSING_GRACE_SECONDS seconds after the first poll. The error carries the last [PodInfo](/developers/sdk/reference/models/pod-info), its status, the status history and the cause the backend recorded (cause), so a caller can tell a dead pod from a slow one and clean up instead of retrying.

scp​

def scp(pod: lium.sdk.models.PodInfo, *, local: str, remote: str) -> None:

Upload a local file to a pod via SFTP.

remote is a file path, or a directory: an existing remote directory, or a path ending in / (created if missing), receives the file under its own name.

download​

def download(pod: lium.sdk.models.PodInfo, *, remote: str, local: str) -> None:

Download a file from a pod via SFTP.

Arguments:

  • pod: The pod to download from.
  • remote: Remote file path on the pod.
  • local: Local destination path.

Raises:

  • ValueError: If SSH is not configured for the pod.

upload​

def upload(pod: lium.sdk.models.PodInfo, *, local: str, remote: str) -> None:

Upload a file to a pod via SFTP.

This is an alias for scp() for parity with the CLI.

Arguments:

  • pod: The pod to upload to.
  • local: Local file path to upload.
  • remote: Remote destination path on the pod.

Raises:

  • ValueError: If SSH is not configured for the pod.

ssh_argv​

def ssh_argv(pod: lium.sdk.models.PodInfo) -> List[str]:

The OpenSSH argument list that opens a shell on a pod.

Built from the user, host and port the API's ssh_connect_cmd names (ssh_target(); any other shape is refused), with -i <configured key> when one is configured and the host-key options of openssh_host_key_options(). Run it with subprocess.run(argv) — no shell is involved, so nothing in the API's value is ever interpreted.

Raises:

  • ValueError: The pod has no SSH command, or it is not ssh <user>@<host> [-p <port>].

refresh_pod​

def refresh_pod(pod: Union[str, lium.sdk.models.PodInfo]) -> lium.sdk.models.PodInfo:

Re-read one pod from the API.

A [PodInfo](/developers/sdk/reference/models/pod-info) is a snapshot: after a restart the pod's host, port and ssh_cmd can all change, and the copy a caller holds says nothing about it. Call this before reconnecting to a pod held for a while.

Arguments:

  • pod: A [PodInfo](/developers/sdk/reference/models/pod-info), a pod id or a huid.

Returns: The current [PodInfo](/developers/sdk/reference/models/pod-info) for that pod (one /pods call).

Raises:

  • LiumNotFoundError: The pod is no longer in the account's pod list.

ssh​

def ssh(pod: lium.sdk.models.PodInfo, *, refresh: bool = False) -> str:

Get SSH command string for connecting to a pod.

The shell-quoted form of ssh_argv(): the configured key, the pinned host-key options and the pod's user@host, ready to paste into a POSIX shell. Building it creates the pod's ~/.lium/known_hosts/<pod id> file when it does not exist yet (empty until the first connection pins the key).

Arguments:

  • pod: The pod to generate SSH command for.
  • refresh: Re-read the pod first (see refresh_pod()), so the command reflects the host and port the pod has now, not the ones it had when pod was fetched.

Returns: SSH command string with the configured SSH key path.

Raises:

  • ValueError: If SSH is not configured for the pod or no SSH key path is set.
  • LiumNotFoundError: refresh=True and the pod is gone.

rsync_options​

def rsync_options(
*,
bwlimit: Optional[int] = None,
exclude: Optional[Sequence[str]] = None,
delete: bool = False,
partial: bool = True,
progress: bool = False
) -> List[str]:

The rsync flags shared by rsync() and cp().

partial keeps half-copied files so an interrupted transfer resumes instead of starting over — multi-GB pulls over a flaky link are the normal case, not the exception. It stays --partial only: --inplace would drop rsync's write-then-rename, and a job on the pod could read a half-written checkpoint. progress is plain --progress, which both GNU rsync and the openrsync stock macOS ships accept (--info=progress2 needs GNU rsync >= 3.1 and dies before any byte moves on a Mac).

rsync​

def rsync(
pod: lium.sdk.models.PodInfo,
*,
local: str,
remote: str,
bwlimit: Optional[int] = None,
exclude: Optional[Sequence[str]] = None,
delete: bool = False,
partial: bool = True,
progress: bool = False,
download: bool = False
) -> None:

Sync files between the local machine and a pod with rsync.

Arguments:

  • pod: Pod to sync.
  • local: Local path (source, or destination when download).
  • remote: Path on the pod (destination, or source when download).
  • bwlimit: Cap the transfer at this many KiB/s (rsync --bwlimit).
  • exclude: Patterns to skip (rsync --exclude), e.g. [".git", "*.pt"].
  • delete: Remove files at the destination that are not in the source.
  • partial: Keep partially transferred files so a retry resumes (default on).
  • progress: Show rsync's overall progress on the terminal instead of capturing its output.
  • download: Copy from the pod to the local path instead of to it.

Raises:

  • RuntimeError: If the rsync command fails.

cp​

def cp(
src_pod: lium.sdk.models.PodInfo,
src_path: str,
dst_pod: lium.sdk.models.PodInfo,
dst_path: str,
*,
bwlimit: Optional[int] = None,
exclude: Optional[Sequence[str]] = None,
delete: bool = False
) -> Dict[str, Any]:

Copy files from one pod to another over SSH, without passing through this machine.

Pod-to-pod links are far faster than relaying through the caller. This client makes a one-off ed25519 key, places the private half on the source pod (sent over the exec session's stdin, never on a command line) and adds the public half to the destination pod's authorized_keys for the duration of the copy; the source runs rsync straight to the destination, and both halves are removed again whatever happened. The source pod's output never decides what the destination trusts: the key the destination authorizes is the one this client generated, and the revoke removes exactly that key. The source verifies the destination with the host key this client pinned for it (~/.lium/known_hosts/<pod id>, written by the grant connection), copied next to the transfer key; no pin means no copy (LIUM_SSH_INSECURE=1 accepts any key, as everywhere).

Arguments:

  • src_pod, src_path: Where to copy from. A trailing / on a directory copies its contents, as in rsync.
  • dst_pod, dst_path: Where to copy to.
  • bwlimit, exclude, delete: As in rsync().

Returns: The exec() result of the rsync on the source pod.

Raises:

  • LiumHostKeyError: When nothing is pinned for the destination pod.
  • LiumError: When the copy fails; the message carries rsync's stderr (rsync: command not found means apt-get install -y rsync on the pod named).

transfer_keypair​

def transfer_keypair() -> Tuple[str, str]:

A fresh ed25519 key pair for one copy, as OpenSSH text.

Returns: (private_key, public_key): the private key in OpenSSH PEM form (what ssh -i reads) and the public key as one ssh-ed25519 <base64> line with no comment.

grant_transfer_key_command​

def grant_transfer_key_command(cls, authorized_line: str) -> str:

The remote line that appends authorized_line to the pod's authorized_keys.

revoke_transfer_key_command​

def revoke_transfer_key_command(cls, public_key: str, marker: str) -> str:

The remote line that drops every authorized_keys entry carrying public_key.

The match is the key itself (ssh-ed25519 <base64>, unique to one cp run), not the comment: a line that lost its marker still goes. The scratch file carries the marker, so two cp runs into the same pod never share one, and the result is written back with cat > (the way lium-io removes keys), so the file keeps its mode and a second run's half-written scratch file can never replace it. grep exits 1 when nothing is left to keep, which is fine; any other failure leaves authorized_keys untouched. The whole line runs under the transfer-key lock, so a concurrent grant or revoke waits for it.

switch_template​

def switch_template(
pod: lium.sdk.models.PodInfo,
*,
template_id: str
) -> lium.sdk.models.PodInfo:

Switch the template of a running pod.

Arguments:

  • pod: Pod to update.
  • template_id: ID of the template to switch to.

Returns: PodInfo object with updated pod information.

create_template​

def create_template(
name: str,
docker_image: str,
docker_image_digest: str = '',
docker_image_tag: str = 'latest',
ports: Optional[List[int]] = None,
start_command: Optional[str] = None,
**kwargs
) -> lium.sdk.models.Template:

Create a new template.

Arguments:

  • name: Friendly template name.
  • docker_image: Image repository (e.g., "daturaai/pytorch").
  • docker_image_digest: Digest string for pinning (defaults to empty string).
  • docker_image_tag: Image tag (defaults to "latest").
  • ports: Internal ports to expose (defaults to [22, 8000]).
  • start_command: Optional command executed on container start.
  • **kwargs: Additional template fields:
    • category (str): Template category (defaults to "UBUNTU").
    • is_private (bool): Whether template is private (defaults to True).
    • volumes (List[str]): Volume mount paths (defaults to ["/workspace"]).
    • description (str): Template description.
    • environment (Dict[str, str]): Environment variables.
    • entrypoint (str): Container entrypoint.
    • one_time_template (bool): Whether to delete template after pod removal (defaults to False).

Returns: Newly created Template.

wait_template_ready​

def wait_template_ready(
template_id: str,
timeout: int = 300
) -> Optional[lium.sdk.models.Template]:

Wait for template verification to complete.

Arguments:

  • template_id: Template identifier.
  • timeout: Maximum seconds to wait.

Returns: Template when verification succeeds, otherwise None if the timeout expires.

Raises:

  • LiumError: If template verification fails.

me​

def me() -> Dict[str, Any]:

The account the API key belongs to, as GET /users/me returns it.

Useful keys: id, email (when the server sends it), balance.

get_my_user_id​

def get_my_user_id() -> str:

Get the current user's ID.

Returns: The ID returned by /users/me.

update_template​

def update_template(
template_id: str,
name: str,
docker_image: str,
docker_image_digest: str,
docker_image_tag: str = 'latest',
ports: Optional[List[int]] = None,
start_command: Optional[str] = None,
**kwargs
) -> lium.sdk.models.Template:

Update an existing template owned by the caller.

Arguments:

  • template_id: Template identifier.
  • name: Friendly name.
  • docker_image: Image repository.
  • docker_image_digest: Optional digest.
  • docker_image_tag: Image tag.
  • ports: Internal ports to expose.
  • start_command: Startup command.
  • **kwargs: Additional override fields.

Returns: Updated Template.

Raises:

  • ValueError: If the template is missing or not owned by the caller.

wallets​

def wallets() -> List[Dict[str, Any]]:

Get the caller's configured funding wallets.

Returns: Raw wallet records returned by the pay API.

add_wallet​

def add_wallet(bt_wallet: Any) -> tuple[str, str]:

Link your wallet to the user account.

Arguments:

  • bt_wallet: Wallet object exposing coldkey/coldkeypub for signing.

Returns: (app_id, customer_id) parsed from the /tao/create-transfer redirect. The alpha funding flow reuses them for the company-wallet lookup, so it makes one POST.

Raises:

  • LiumError: If verification or wallet polling fails.

alpha_subnets​

def alpha_subnets() -> lium.sdk.client.AlphaSubnets:

The subnets whose alpha Lium accepts as payment (GET /balance/alpha/subnets).

Hard-fails (no fallback): the pay API answers 503 while it has no accepted set, which _request raises as [LiumServerError](/developers/sdk/reference/exceptions/lium-server-error).

convert_alpha​

def convert_alpha(usd: Any, netuid: Optional[int] = None) -> lium.sdk.client.AlphaQuote:

Quote usd (USD) -> alpha via GET /balance/convert/alpha.

The response carries the alpha amount to transfer (converted) and the subnet netuid the transfer goes to. netuid picks one of the accepted subnets (alpha_subnets()); without it the pay API quotes on its primary subnet. A pay-API error raises: _request maps 503 -> [LiumServerError](/developers/sdk/reference/exceptions/lium-server-error) and a subnet that is not accepted (400) to a [LiumError](/developers/sdk/reference/exceptions/lium-error), so the funding stops before any transfer is sent.

company_wallet​

def company_wallet(app_id: str) -> str:

Resolve the Lium deposit address via GET /wallet/company/?app_id=.

Returns the company wallet_hash, the SS58 address Lium credits deposits to. A 404 (app has no wallet) raises [LiumNotFoundError](/developers/sdk/reference/exceptions/lium-not-found-error) (a [LiumError](/developers/sdk/reference/exceptions/lium-error)) before any transfer is sent.

backup_create​

def backup_create(
pod: lium.sdk.models.PodInfo,
*,
path: str,
frequency_hours: int = 6,
retention_days: int = 7
) -> lium.sdk.models.BackupConfig:

Create or replace a backup configuration for a pod.

Arguments:

  • pod: Pod to configure.
  • path: Explicit filesystem path inside the pod volume to back up.
  • frequency_hours: Backup interval in hours.
  • retention_days: Retention period in days.

Returns: Created BackupConfig.

backup_update​

def backup_update(
config_id: str,
*,
path: str,
frequency_hours: int,
retention_days: int
) -> lium.sdk.models.BackupConfig:

Replace the schedule of an existing backup configuration in place.

A refused update leaves the old configuration as it was.

Arguments:

  • config_id: Backup configuration identifier.
  • path: Explicit filesystem path inside the pod volume to back up.
  • frequency_hours: Backup interval in hours.
  • retention_days: Retention period in days.

Returns: Updated BackupConfig.

backup_now​

def backup_now(
pod: lium.sdk.models.PodInfo,
*,
name: str,
description: str = ''
) -> Dict[str, Any]:

Trigger an immediate backup for a pod.

Arguments:

  • pod: Pod to back up.
  • name: Backup name.
  • description: Optional description.

Returns: API response payload from the run-now endpoint.

backup_config​

def backup_config(pod: lium.sdk.models.PodInfo) -> Optional[lium.sdk.models.BackupConfig]:

Return the backup configuration for a pod if one exists.

Arguments:

  • pod: Pod to inspect.

Returns: BackupConfig if present, otherwise None.

backup_list​

def backup_list() -> List[lium.sdk.models.BackupConfig]:

List all backup configurations across all pods.

Returns: List of BackupConfig.

backup_logs​

def backup_logs(pod: lium.sdk.models.PodInfo) -> List[lium.sdk.models.BackupLog]:

Get recent backup logs for a pod.

Arguments:

  • pod: Pod to inspect.

Returns: List of BackupLog entries (possibly empty).

backup_logs_all​

def backup_logs_all() -> List[lium.sdk.models.BackupLog]:

Get all backup logs available to the current user.

backup_log​

def backup_log(backup_id: str) -> lium.sdk.models.BackupLog:

Get one backup log owned by the authenticated user.

resolve_backup_id​

def resolve_backup_id(backup_id: str) -> str:

Resolve an eight-character backup ID shown by the CLI.

backup_delete​

def backup_delete(config_id: str) -> Dict[str, Any]:

Delete a backup configuration by ID.

Arguments:

  • config_id: Backup configuration identifier.

Returns: API response payload.

backup_cancel​

def backup_cancel(backup_id: str) -> Dict[str, Any]:

Request cancellation of an active backup while retaining its history.

backup_log_delete​

def backup_log_delete(backup_id: str) -> Dict[str, Any]:

Delete the stored data for a completed backup and retain its audit row.

restore​

def restore(
pod: lium.sdk.models.PodInfo,
*,
backup_id: str,
restore_path: Optional[str] = None
) -> Dict[str, Any]:

Restore a backup to a pod.

Arguments:

  • pod: Pod to restore to.
  • backup_id: ID of the backup to restore.
  • restore_path: New or empty subdirectory where the backup is restored. Defaults to <pod volume>/restored.

Returns: Response from the restore API.

restore_logs​

def restore_logs(pod: lium.sdk.models.PodInfo) -> List[lium.sdk.models.RestoreLog]:

Get recent restore logs for a pod.

Arguments:

  • pod: Pod to inspect.

Returns: List of RestoreLog entries (possibly empty).

resolve_restore_id​

def resolve_restore_id(restore_id: str) -> str:

Resolve an eight-character restore ID shown by the CLI.

restore_cancel​

def restore_cancel(restore_id: str) -> Dict[str, Any]:

Request cancellation of an active restore.

get_deployment_estimate​

def get_deployment_estimate(executor_id: str, template_id: str) -> dict:

Estimate deployment time for a template on a node.

Arguments:

  • executor_id: Node UUID.
  • template_id: Template UUID.

Returns: Dict with estimated_seconds, is_slow_machine, warning_message, is_cached_template, and docker_image_size (image size in bytes, or None if unknown).

balance​

def balance() -> float:

Get current account balance.

Pods are billed per second at their hourly price; the balance is debited every 5 minutes.

Returns: Floating-point balance value reported by /users/me.

balance_or_none​

def balance_or_none() -> Optional[float]:

The balance from /users/me, or None when the answer has no balance field — where balance() would say 0, which a caller comparing balances would misread.

events​

def events(
*,
since: Optional[datetime.datetime] = None,
pod_id: Optional[str] = None,
api_key_id: Optional[str] = None,
limit: int = 200
) -> List[Dict[str, Any]]:

The account's event log, newest first (GET /users/me/events).

Each entry is one recorded action — a rent request, creation, reboot, deletion with its reason, an API/SSH key or template change — with actor naming the session or API key (api_key_id, api_key_name) that made the request; null when the platform acted by itself. pod_id also answers for a pod that has since been deleted.

audit_log​

def audit_log(
*,
since: Optional[datetime.datetime] = None,
until: Optional[datetime.datetime] = None,
action: Optional[str] = None,
source: Optional[str] = None,
api_key_id: Optional[str] = None,
resource_id: Optional[str] = None,
cursor: Optional[str] = None,
limit: int = 100
) -> Dict[str, Any]:

One page of the account audit log, newest first (GET /account/audit).

One entry per request that changed something on the account — a pod created, restarted or deleted, a key created or revoked, a login, a top-up requested, a setting or a workspace member changed — with action (pod.delete, key.create, login, …), actor (auth session or api_key, user_id, api_key_id, api_key_name), source (portal, cli, sdk, mcp, admin, api), ip (filled on the caller's own entries only), user_agent, request_id, method, route, status_code, resource_type, resource_id, summary. action filters by prefix (pod. is every pod action). Returns \{"items": [...], "next_cursor": ...\}; pass next_cursor back as cursor for the next (older) page, None when this page was the last. limit is 1–500. A key needs the read scope; a server without the route answers 404 ([LiumNotFoundError](/developers/sdk/reference/exceptions/lium-not-found-error)).

topup_currencies​

def topup_currencies(refresh: bool = False) -> List[Dict[str, Any]]:

List stablecoin currencies/networks supported for self-serve top-ups.

Arguments:

  • refresh: Bypass the server-side cache and re-fetch from the provider.

Returns: List of \{"code", "network", "decimals", "display_decimals"\} dicts.

topup_create_invoice​

def topup_create_invoice(
amount: float,
crypto_currency: str,
crypto_network: str
) -> Dict[str, Any]:

Create a stablecoin top-up invoice for the current account.

The returned deposit_address is where the exact crypto_amount of crypto_currency (on crypto_network) must be sent. Once the provider confirms the transfer, the account balance is credited automatically.

Arguments:

  • amount: Top-up amount in USD.
  • crypto_currency: Stablecoin code (e.g. "USDT"), see topup_currencies().
  • crypto_network: Network the stablecoin is sent on (e.g. "tron").

Returns: Invoice dict including invoice_id, deposit_address, crypto_amount, crypto_currency, crypto_network, exchange_rate and expires_at.

def topup_checkout_link(
amount_usd: float,
success_url: Optional[str] = None,
cancel_url: Optional[str] = None
) -> Dict[str, Any]:

Open a Stripe Checkout page that tops up this account by card (POST /stripe/create-checkout-session).

Nothing is charged by this call: whoever opens url enters a card (and passes the bank's 3-D Secure check, if it asks) and the balance is credited by Stripe's webhook once the payment succeeds. This is how an agent with no card of its own asks a person to pay. The key must hold the billing scope (or be a browser session); a read / rent / manage key is refused with LiumPermissionError.

Arguments:

  • amount_usd: At least $10, the platform's top-up minimum.
  • success_url: Where Checkout sends the payer after paying; default the Billing page of the site the client talks to.
  • cancel_url: Where Checkout sends the payer on cancel; same default.

Returns: \{"url", "session_id", "amount_usd", "expires_at"\}; expires_at is a Unix time, or None when the server does not send it.

wait_for_credit​

def wait_for_credit(
baseline: float,
timeout: float = 600,
interval: float = 2.0,
*,
_clock: Callable[[], float] = <built-in function monotonic>,
_sleep: Callable[[float], NoneType] = <built-in function sleep>
) -> Dict[str, Any]:

Poll the balance until it rises above baseline (a top-up landed) or timeout seconds pass.

Card and crypto top-ups are credited by the payment provider's webhook, so the call that starts a payment returns before the money is on the balance. Read balance() before starting the payment and pass it as baseline. A balance read that fails is retried on the next tick; the pods' own billing can lower the balance meanwhile, so a top-up smaller than what the account's running pods burn in interval seconds may be missed, and any other credit landing meanwhile (a second invoice, a transfer) also ends the wait: compare balance with what you paid.

Returns: \{"credited": bool, "balance": float | None, "seconds": float\}: credited is False when the time ran out; balance is the last one read (None if none could be read).

volumes​

def volumes() -> List[lium.sdk.models.VolumeInfo]:

List all volumes for the current user.

Returns: List of VolumeInfo.

volume​

def volume(volume_id: str) -> lium.sdk.models.VolumeInfo:

Get a specific volume by ID.

Arguments:

  • volume_id: Volume identifier.

Returns: VolumeInfo for the requested volume.

volume_create​

def volume_create(name: str, *, description: str = '') -> lium.sdk.models.VolumeInfo:

Create a new volume.

Arguments:

  • name: Volume name.
  • description: Optional description.

Returns: Created VolumeInfo.

volume_update​

def volume_update(
volume_id: str,
*,
name: Optional[str] = None,
description: Optional[str] = None
) -> lium.sdk.models.VolumeInfo:

Update a volume's metadata.

Arguments:

  • volume_id: Volume identifier.
  • name: Optional new name.
  • description: Optional description.

Returns: Updated VolumeInfo.

Raises:

  • ValueError: If neither name nor description is provided.

volume_delete​

def volume_delete(volume_id: str) -> Dict[str, Any]:

Delete a volume.

Arguments:

  • volume_id: Volume identifier.

Returns: API response payload from the delete request.

schedule_termination​

def schedule_termination(
pod: Union[str, lium.sdk.models.PodInfo, Dict],
*,
termination_time: str
) -> Dict[str, Any]:

Schedule a pod for automatic termination at a future date and time.

The pod does not have to be running: the dict up() returns, or its id, is enough, so the schedule can be set before wait_ready() — a pod that never becomes ready is billed all the same and is removed at termination_time.

Arguments:

  • pod: Pod identifier, PodInfo (or any object with an id attribute), or dict with an id field.
  • termination_time: ISO 8601 formatted datetime string (e.g., "2025-10-17T15:30:00Z")

Returns: Response from the schedule termination API

cap_spend​

def cap_spend(pod: lium.sdk.models.PodInfo, *, budget_usd: float) -> datetime.datetime:

Schedule the pod's removal for the moment it will have spent budget_usd.

The API caps a rental by time only. This computes the time at which the pod, billed at its hourly price since created_at, reaches the budget (lium.sdk.utils.spend_cap_deadline()) and schedules removal then — unless a removal is already scheduled earlier (a --ttl), which stays, as lium up --budget --ttl keeps the earlier of the two. Client-side: the pod keeps running if the schedule is canceled or the price changes.

Arguments:

  • pod: A running pod with created_at and an executor price.
  • budget_usd: Total spend allowed for the pod's lifetime.

Returns: The scheduled removal time (UTC): the budget deadline, or the earlier removal that was already scheduled.

Raises:

  • ValueError: Budget not positive, price or creation time unknown, the budget is already spent (the deadline is in the past), or the pod's scheduled removal has already passed.

cancel_scheduled_termination​

def cancel_scheduled_termination(pod: lium.sdk.models.PodInfo) -> Dict[str, Any]:

Cancel a scheduled termination for a pod.

Arguments:

  • pod: Pod to cancel the schedule for

Returns: Response from the cancel scheduled termination API

install_jupyter​

def install_jupyter(
pod: lium.sdk.models.PodInfo,
*,
jupyter_internal_port: int
) -> Dict[str, Any]:

Install Jupyter Notebook on a pod.

Arguments:

  • pod: Pod to install Jupyter on
  • jupyter_internal_port: Internal port for Jupyter Notebook

Returns: Response from the install Jupyter API