---
title: Lium
sidebar_label: Lium
sidebar_position: 1
---

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

<!-- Generated by scripts/generate_sdk_reference.py. Do not edit directly. -->

# Lium

```python
from lium.sdk import Lium
```

Defined in `lium.sdk.client`.

Clean Unix-style SDK for Lium.

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

## Attributes

| Name | Type | Description |
| --- | --- | --- |
| `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

| Method | Description |
| --- | --- |
| [`features`](#features) | Optional API capabilities the backend advertises on GET /version. |
| [`supports`](#supports) | Whether the backend advertises feature (see features()). |
| [`list_ssh_keys`](#list_ssh_keys) | Return SSH keys registered for the current user. |
| [`register_ssh_key`](#register_ssh_key) | Register a new SSH public key under the current user. |
| [`default_ssh_key_name`](#default_ssh_key_name) | cli-&lt;user&gt;@&lt;host&gt; sanitised to A-Za-z0-9.@-. |
| [`up`](#up) | Start a new pod on a specific node. |
| [`pod_by_name`](#pod_by_name) | The pod called name — or whose huid or id is name — if it exists. |
| [`rental`](#rental) | Rent a pod on executorid for the duration of a with block and always remove it. |
| [`rent`](#rent) | Rent the cheapest available node that satisfies a spec, without listing the fleet. |
| [`pod`](#pod) | Retrieve detailed information about a specific pod. |
| [`logs`](#logs) | Stream logs from a pod. |
| [`edit`](#edit) | Edit a pod's template configuration. |
| [`ls`](#ls) | List available nodes. |
| [`ps`](#ps) | List active pods. |
| [`billing_statement`](#billing_statement) | The account's pod charges grouped by pod (GET /billing/statement), most recently billed first. |
| [`down`](#down) | Stop a pod. |
| [`rm`](#rm) | Remove pod (alias for down()). |
| [`reboot`](#reboot) | Reboot a pod. |
| [`clusters`](#clusters) | Groups of free nodes that sit on one RDMA fabric and can be rented as one cluster. |
| [`cluster_offer`](#cluster_offer) | The offer for one fabric (exact id, or a unique prefix of it). |
| [`cluster_template`](#cluster_template) | The template every cluster rental must use (daturaai/lium-cluster); newest tag wins. |
| [`up_cluster`](#up_cluster) | Rent several nodes of one fabric as a single all-or-nothing cluster. |
| [`my_clusters`](#my_clusters) | The caller's cluster rentals, one Cluster per clusterid found in ps(). |
| [`cluster`](#cluster) | One cluster by id (exact, or a unique prefix), from the caller's pods. |
| [`wait_cluster_ready`](#wait_cluster_ready) | Poll until every member of cluster is RUNNING with SSH metadata. |
| [`rm_cluster`](#rm_cluster) | Remove every member pod of a cluster with one DELETE /clusters/\{clusterid\}. |
| [`get_default_images`](#get_default_images) | Get default images for GPU type and driver version. |
| [`default_docker_template`](#default_docker_template) | Resolve the best default template for a node ID. |
| [`templates`](#templates) | List available templates. |
| [`get_executor`](#get_executor) | Resolve a node by UUID or HUID against the same listing ls() returns. |
| [`ambiguous_executor_message`](#ambiguous_executor_message) | The sentence for a HUID that is shared by several listed nodes. |
| [`executor_not_found_message`](#executor_not_found_message) | The one sentence every caller prints when a node id resolves to nothing. |
| [`gpu_types`](#gpu_types) | Get list of available GPU types. |
| [`gpu_short_types`](#gpu_short_types) | The short GPU types the marketplace knows (H100, RTX4090, ...), sorted. |
| [`unknown_gpu_type`](#unknown_gpu_type) | None when gpushort names a known GPU type; otherwise the list of known types. |
| [`request_machine`](#request_machine) | Ask providers for a GPU type that is sold out (POST /machine-requests). |
| [`get_template`](#get_template) | Fetch a template by ID/HUID/name. |
| [`get_template_by_image_name`](#get_template_by_image_name) | Fetch a template by its Docker image + tag. |
| [`ssh_connection`](#ssh_connection) | SSH connection context manager. |
| [`ssh_session`](#ssh_session) | Keep one SSH connection to pod open for the whole block. |
| [`exec`](#exec) | Execute a shell command on a pod over SSH. |
| [`default_detach_log_path`](#default_detach_log_path) | /workspace/logs/exec-&lt;UTC stamp&gt;-&lt;6 hex&gt;.log; the tail keeps two. |
| [`build_detached_command`](#build_detached_command) | The remote command line that starts command in the background and prints its PID. |
| [`login_shell_env`](#login_shell_env) | (prelude, execenv) that make a bash -lc job see env. |
| [`run_background`](#run_background) | Start command in the background on a pod and return a Job to follow it. |
| [`job`](#job) | Re-attach to a job started earlier with runbackground(), by name. |
| [`jobs`](#jobs) | Every job that has a PID file under jobdir on the pod, running or finished. |
| [`wait_for_port`](#wait_for_port) | Block until TCP port accepts connections inside the pod (a server that is up). |
| [`parse_gpu_stats`](#parse_gpu_stats) | Parse nvidia-smi --query-gpu=... --format=csv,noheader,nounits output. |
| [`gpu_stats`](#gpu_stats) | Per-GPU utilization, memory, temperature and power on a pod, via nvidia-smi. |
| [`stream_exec`](#stream_exec) | Execute a shell command and stream incremental output. |
| [`exec_all`](#exec_all) | Execute a shell command on multiple pods in parallel. |
| [`poll_delay`](#poll_delay) | Seconds to sleep between two waitready polls. |
| [`pod_events`](#pod_events) | The pod's event log, oldest first — creation, reboots, failures with their error, and. |
| [`pod_failure_cause`](#pod_failure_cause) | What the backend recorded as the reason the pod failed or was closed, or None. |
| [`wait_ready`](#wait_ready) | Poll until a pod reports RUNNING + SSH metadata. |
| [`scp`](#scp) | Upload a local file to a pod via SFTP. |
| [`download`](#download) | Download a file from a pod via SFTP. |
| [`upload`](#upload) | Upload a file to a pod via SFTP. |
| [`ssh_argv`](#ssh_argv) | The OpenSSH argument list that opens a shell on a pod. |
| [`refresh_pod`](#refresh_pod) | Re-read one pod from the API. |
| [`ssh`](#ssh) | Get SSH command string for connecting to a pod. |
| [`rsync_options`](#rsync_options) | The rsync flags shared by rsync() and cp(). |
| [`rsync`](#rsync) | Sync files between the local machine and a pod with rsync. |
| [`cp`](#cp) | Copy files from one pod to another over SSH, without passing through this machine. |
| [`transfer_keypair`](#transfer_keypair) | A fresh ed25519 key pair for one copy, as OpenSSH text. |
| [`grant_transfer_key_command`](#grant_transfer_key_command) | The remote line that appends authorizedline to the pod's authorizedkeys. |
| [`revoke_transfer_key_command`](#revoke_transfer_key_command) | The remote line that drops every authorizedkeys entry carrying publickey. |
| [`switch_template`](#switch_template) | Switch the template of a running pod. |
| [`create_template`](#create_template) | Create a new template. |
| [`wait_template_ready`](#wait_template_ready) | Wait for template verification to complete. |
| [`me`](#me) | The account the API key belongs to, as GET /users/me returns it. |
| [`get_my_user_id`](#get_my_user_id) | Get the current user's ID. |
| [`update_template`](#update_template) | Update an existing template owned by the caller. |
| [`wallets`](#wallets) | Get the caller's configured funding wallets. |
| [`add_wallet`](#add_wallet) | Link your wallet to the user account. |
| [`alpha_subnets`](#alpha_subnets) | The subnets whose alpha Lium accepts as payment (GET /balance/alpha/subnets). |
| [`convert_alpha`](#convert_alpha) | Quote usd (USD) -&gt; alpha via GET /balance/convert/alpha. |
| [`company_wallet`](#company_wallet) | Resolve the Lium deposit address via GET /wallet/company/?appid=. |
| [`backup_create`](#backup_create) | Create or replace a backup configuration for a pod. |
| [`backup_now`](#backup_now) | Trigger an immediate backup for a pod. |
| [`backup_config`](#backup_config) | Return the backup configuration for a pod if one exists. |
| [`backup_list`](#backup_list) | List all backup configurations across all pods. |
| [`backup_logs`](#backup_logs) | Get recent backup logs for a pod. |
| [`backup_logs_all`](#backup_logs_all) | Get all backup logs available to the current user. |
| [`backup_log`](#backup_log) | Get one backup log owned by the authenticated user. |
| [`resolve_backup_id`](#resolve_backup_id) | Resolve an eight-character backup ID shown by the CLI. |
| [`backup_delete`](#backup_delete) | Delete a backup configuration by ID. |
| [`backup_cancel`](#backup_cancel) | Request cancellation of an active backup while retaining its history. |
| [`backup_log_delete`](#backup_log_delete) | Delete the stored data for a completed backup and retain its audit row. |
| [`restore`](#restore) | Restore a backup to a pod. |
| [`restore_logs`](#restore_logs) | Get recent restore logs for a pod. |
| [`resolve_restore_id`](#resolve_restore_id) | Resolve an eight-character restore ID shown by the CLI. |
| [`restore_cancel`](#restore_cancel) | Request cancellation of an active restore. |
| [`get_deployment_estimate`](#get_deployment_estimate) | Estimate deployment time for a template on a node. |
| [`balance`](#balance) | Get current account balance. |
| [`balance_or_none`](#balance_or_none) | The balance from /users/me, or None when the answer has no balance field — where. |
| [`events`](#events) | The account's event log, newest first (GET /users/me/events). |
| [`audit_log`](#audit_log) | One page of the account audit log, newest first (GET /account/audit). |
| [`topup_currencies`](#topup_currencies) | List stablecoin currencies/networks supported for self-serve top-ups. |
| [`topup_create_invoice`](#topup_create_invoice) | Create a stablecoin top-up invoice for the current account. |
| [`topup_checkout_link`](#topup_checkout_link) | Open a Stripe Checkout page that tops up this account by card (POST /stripe/create-checkout-session). |
| [`wait_for_credit`](#wait_for_credit) | Poll the balance until it rises above baseline (a top-up landed) or timeout seconds pass. |
| [`volumes`](#volumes) | List all volumes for the current user. |
| [`volume`](#volume) | Get a specific volume by ID. |
| [`volume_create`](#volume_create) | Create a new volume. |
| [`volume_update`](#volume_update) | Update a volume's metadata. |
| [`volume_delete`](#volume_delete) | Delete a volume. |
| [`schedule_termination`](#schedule_termination) | Schedule a pod for automatic termination at a future date and time. |
| [`cap_spend`](#cap_spend) | Schedule the pod's removal for the moment it will have spent budgetusd. |
| [`cancel_scheduled_termination`](#cancel_scheduled_termination) | Cancel a scheduled termination for a pod. |
| [`install_jupyter`](#install_jupyter) | Install Jupyter Notebook on a pod. |

### features

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

```python
def supports(feature: str) -> bool:
```

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

### list_ssh_keys

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

Return SSH keys registered for the current user.

### register_ssh_key

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

```python
def default_ssh_key_name() -> str:
```

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

### up

```python
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`](/developers/sdk/reference/models/pod-info). The pod is billing
   from the moment the rent call returns, so a timeout raises a
   [`LiumError`](/developers/sdk/reference/exceptions/lium-error) 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`](/developers/sdk/reference/models/pod-info) when ``wait`` is ``True``.

### pod_by_name

```python
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`](/developers/sdk/reference/models/pod-info), or ``None``.

### rental

```python
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`](/developers/sdk/reference/models/rent-result)
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`](/developers/sdk/reference/models/pod-info).

**Example:**
```python
with lium.rental(executor_id=node.id, name="job") as pod:
```
...     lium.exec(pod, command="nvidia-smi")

### rent

```python
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`](/developers/sdk/reference/models/rent-result) — 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

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

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

```python
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:**
```python
lium.edit(pod_id, startup_commands="python main.py", environment={"DEBUG": "1"})
```

### ls

```python
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`](/developers/sdk/reference/models/executor-info#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`](/developers/sdk/reference/models/executor-info#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`](/developers/sdk/reference/models/executor-info#specs) (docker
   info, verified ports, per-GPU telemetry, checksums).

**Returns:**
A list of [`ExecutorInfo`](/developers/sdk/reference/models/executor-info) objects that satisfy the filters.

### ps

```python
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`](/developers/sdk/reference/models/pod-info) objects representing the caller's running pods.

### billing_statement

```python
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 per-UTC-day
rows; 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

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

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

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

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

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

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

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

### up_cluster

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

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

The caller's cluster rentals, one [`Cluster`](/developers/sdk/reference/models/cluster) per ``cluster_id`` found in `ps()`.

### cluster

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

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

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

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

```python
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`](/developers/sdk/reference/models/template) best suited for the node.

**Raises:**
 - **ValueError:**  If no matching node or template exists.

### templates

```python
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`](/developers/sdk/reference/models/template).

### get_executor

```python
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`](/developers/sdk/reference/models/executor-info) 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

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

```python
def executor_not_found_message(executor: str) -> str:
```

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

### gpu_types

```python
def gpu_types() -> set[str]:
```

Get list of available GPU types.

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

### gpu_short_types

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

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

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

```python
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`](/developers/sdk/reference/models/template) or ``None`` if not found.

### get_template_by_image_name

```python
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`](/developers/sdk/reference/models/template) or ``None`` if not found.

### ssh_connection

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

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

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

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

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

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

```python
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`](/developers/sdk/reference/jobs/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

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

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

```python
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()`](/developers/sdk/reference/jobs/job#wait_for_port), which also
stops early when the job dies.

**Raises:**
 - **TimeoutError:**  the port did not answer within ``timeout`` seconds.

### parse_gpu_stats

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

```python
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`](/developers/sdk/reference/models/template).

### wait_template_ready

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

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

```python
def get_my_user_id() -> str:
```

Get the current user's ID.

**Returns:**
The ID returned by ``/users/me``.

### update_template

```python
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`](/developers/sdk/reference/models/template).

**Raises:**
 - **ValueError:**  If the template is missing or not owned by the caller.

### wallets

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

Get the caller's configured funding wallets.

**Returns:**
Raw wallet records returned by the pay API.

### add_wallet

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

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

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

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

```python
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`](/developers/sdk/reference/models/backup-config).

### backup_now

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

```python
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`](/developers/sdk/reference/models/backup-config) if present, otherwise ``None``.

### backup_list

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

List all backup configurations across all pods.

**Returns:**
List of [`BackupConfig`](/developers/sdk/reference/models/backup-config).

### backup_logs

```python
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`](/developers/sdk/reference/models/backup-log) entries (possibly empty).

### backup_logs_all

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

Get all backup logs available to the current user.

### backup_log

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

Get one backup log owned by the authenticated user.

### resolve_backup_id

```python
def resolve_backup_id(backup_id: str) -> str:
```

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

### backup_delete

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

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

Request cancellation of an active backup while retaining its history.

### backup_log_delete

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

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

### restore

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

```python
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`](/developers/sdk/reference/models/restore-log) entries (possibly empty).

### resolve_restore_id

```python
def resolve_restore_id(restore_id: str) -> str:
```

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

### restore_cancel

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

Request cancellation of an active restore.

### get_deployment_estimate

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

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

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

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

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

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

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

### topup_checkout_link

```python
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`](/developers/sdk/reference/exceptions/lium-permission-error).

**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

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

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

List all volumes for the current user.

**Returns:**
List of [`VolumeInfo`](/developers/sdk/reference/models/volume-info).

### volume

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

Get a specific volume by ID.

**Arguments:**
 - **volume_id:**  Volume identifier.

**Returns:**
[`VolumeInfo`](/developers/sdk/reference/models/volume-info) for the requested volume.

### volume_create

```python
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`](/developers/sdk/reference/models/volume-info).

### volume_update

```python
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`](/developers/sdk/reference/models/volume-info).

**Raises:**
 - **ValueError:**  If neither ``name`` nor ``description`` is provided.

### volume_delete

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

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

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

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

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