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

# Python SDK

> The whole surface is twelve names — one operation per job, arguments select behavior. Everything else is an alias.

The SDK is **LithosBox** (`import lithosbox`; class `LithosBox`; `Nanovm` and
`import nanovm` remain aliases). Sync and async (`AsyncNanovm`) with identical
surfaces. Constructor: `api_url`, `token` — or `auth_url`/`client_id`/`client_secret` —
and `timeout`; all read `NANOVM_*` env vars when omitted.

**The whole surface is twelve names.** Each is one operation whose arguments
select the behavior — there is one way to run a command, one way to read
metrics, one way to put a sandbox away. Every argument listed here is the whole
contract; there are no other public methods worth learning (older names are
silent aliases).

### `nv.sandboxes.create(...)`

`create(image=None, template=None, snapshot_id=None, cpus=None, memory_mb=None, disable_internet=False, runtime=None, vm_id=None, wait_for_template=True, warm_timeout=300)`

One source: an OCI `image` ref, a `template` (name, `name:version`, or id), or a
`snapshot_id` to restore into a NEW sandbox; none = default sandbox (Python 3.12 +
Node 22). Shape: `cpus` 1–4, `memory_mb` 128–8192 (defaults 2/1024).
`runtime="vm"` gives full-VM semantics (real /dev, dockerd works). `vm_id` (a
UUID you mint) makes the create retry-safe. Returns a `Sandbox`; context-manager
deletes on exit. Also `get(id)`, `list()`, `delete(id)`.

### `sb.run(command, background=False, stdin=None) -> ExecResult`

THE command verb. `command` str = shell (`sh -c`: pipes, globs, redirects).
`command` list = argv, no shell — the only safe way to pass untrusted values.
Blocks until the command and its children finish (\~110 s wall at the edge):
`.exit_code/.stdout/.stderr` (+ `.stdout_truncated` past the 16 MiB stream cap).
`background=True` (shell string only) returns immediately and the process
outlives the call, keeping the sandbox awake. `stdin` bytes/str ≤ 4 MiB (the
way to ship files/binary data — argv strings cap at 128 KiB and cannot hold NUL).

### `sb.session(session_id=None) -> Session`

A shell where `cd`, `export`, venvs and background jobs persist across
`.run(cmd)` calls (256 concurrent per sandbox; same id from any handle = same
shell; `.close()` ends it for everyone). Use for multi-step shell work; use
plain `run()` for everything else.

### `sb.files.*`

`write(path, data) / read(path) / read_bytes(path) / list(path) / remove(path) / rename(src, dst) / mkdir(path) / exists(path)` —
all file work (4 MiB per write, 16 MiB per read; loop for bigger). `remove` is
recursive. `write` creates parents.

### `sb.expose(guest_port, public_port=0) -> IngressInfo`

Public HTTPS URL (`.url`) for a port the guest listens on. Live once the server
answers; survives sleep/wake; re-installs across archive/unarchive (may 404 for
seconds). `sb.ingress()` lists, `nv.ingress.delete(id)` removes.

### `sb.archive()` / `sb.unarchive()`

Put the sandbox away: memory+disk to object storage, host freed, compute
billing stopped, **survives node loss**; composes from any rest state.
Unarchive restores it running, processes intact (seconds). A later `run()` on
an archived sandbox raises — unarchive first.

### `sb.snapshot(leave_paused=False, wait_durable=False) -> Snapshot`

Durable checkpoint of memory+disk (\~50 ms; durable seconds later —
`wait_durable=True` blocks for it). Restore into a NEW sandbox:
`nv.snapshots.restore(snap)` or `create(snapshot_id=...)`. Snapshots outlive
their sandbox. `nv.snapshots.list/get/delete`.

### `sb.fork(n=1) -> Sandbox | list[Sandbox]`

Live copies from this instant — running processes and open connections included
(n ≤ 4 per call; fork between commands, not mid-command). One checkpoint, n
parallel restores.

### `sb.events(limit=200) -> list[dict]`

The lifecycle timeline, newest first: `{kind, detail, node, at}` for
created/paused/resumed/slept/archived/unarchived/rebooted/snapshotted/forked/deleted.
Survives deletion.

### `sb.logs(tail_bytes=65536) -> str`

Serial-console tail (boot output, kernel messages; ≤ 256 KiB per read).
Host-side: never wakes a paused/slept guest. Archived sandboxes have no live
console (their `events()` and `metrics(hours=…)` still answer).

### `sb.metrics(hours=0, start="", end="") -> dict`

No arguments: `{"samples": [...], "cpus": n}` — live 30 s host samples, last
\~2 h (`cpu_ms`, `mem_mb`, `rx/tx_bytes`, `interval_s`; CPU fraction =
`cpu_ms/(interval_s*1000*cpus)`). With `hours=` or `start=`/`end=` (RFC3339,
≤ 15 days back): `{"points": [...]}` — per-minute ledger history, including
archived and deleted sandboxes; a point with `measured=false` is a gap, never a
zero (normalize by its `util_seconds`).

### `nv.usage(start=None, end=None)` / `nv.audit(limit=200, before=None)`

Org metering aggregates (per-state vm/cpu/memory seconds + snapshot
byte-seconds) and the org's audit trail of mutating API calls
(`{id, key_id, action, resource, outcome, request_id, at}`; page with
`before=<last id>`).

***

That is the product. Everything below is for when you need it — nothing there
changes how the twelve behave.

<Accordion title="Advanced — finer rest states, templates, registries, exports">
  | Operation                                     | Notes                                                                                                   |
  | --------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
  | `sb.pause()` / `sb.resume()`                  | ms-scale suspend, RAM held on the host (archive is the durable version)                                 |
  | `sb.sleep()`                                  | hint the idle reclaimer; the platform already does this automatically, and any call wakes transparently |
  | `sb.reboot()`                                 | fresh boot, disk kept, processes/sessions gone, new guest IP                                            |
  | `nv.templates.create/list/get/wait/delete`    | build an image once, create from it fast                                                                |
  | `nv.registries.add/list/remove`               | private image registry credentials                                                                      |
  | `snapshot.export()` + `nv.exports.get/delete` | download a snapshot as a zstd image                                                                     |
  | `GET /usage/series`                           | the activity-graph time series behind the console's Usage charts                                        |
  | `nv.rate`                                     | client-side view of the rate-limit headers (`nv.rate.acquire/.ops`; pace on `.remaining`)               |

  Older method names from 0.4.x (`exec`, `exec_background`, `stop`/`start`,
  `branch`, `files.ls`) keep working forever as aliases of the twelve — existing
  code never breaks — but new code and these docs use one name per operation.
</Accordion>

## Errors

`AuthError` (401/403 → key), `NotFoundError` (404), `ConflictError` (409:
template not ready / sandbox stopped), `TemplateBuildingError` (503: first-use
build, `create()` waits), `ServerError` (5xx: retry idempotent calls). A 503
whose text says "connection error" is a transient control-plane restart: retry
in a few seconds. `SandboxUnhealthyError` (503: the guest stopped answering — a
fork bomb or OOM inside it): `reboot()` or `sb.delete()` and create a new one;
archiving cannot checkpoint a starved guest, so it fails. `TransportError`: the
connection failed before a response — idempotent calls were already retried;
for a create the outcome is UNKNOWN, so `nv.sandboxes.list()` before retrying
(never double-create blindly).

`QuotaError` (429/403) is one of two things — read `.kind`: `"rate"` = the
organization's per-minute request budget is spent; wait `.retry_after` seconds
(from `Retry-After`) and retry. Creates, snapshots, forks, exposes, templates
and exports share one budget; exec and other operations a much larger one;
**DELETE is never rate limited**, so releasing resources always works. `"cap"` =
the concurrent-sandbox cap; delete or archive a sandbox, then retry — waiting
does not help. Never loop on a 429 without waiting. Full status-code table:
[HTTP API](/reference/http-api#status-codes).
