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.
Advanced — finer rest states, templates, registries, exports
Advanced — finer rest states, templates, registries, exports
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.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.