Skip to main content
Use the Python SDK from an environment configured by the installer. The examples below use box for the client and sandbox for a sandbox returned by box.sandboxes.create() or box.sandboxes.get().

Configure the client

Run scripts with lithosbox python to use the key you saved during installation. For other Python environments, provide LITHOSBOX_TOKEN through the environment or pass token= to the client. Keep credentials outside your source code. Keep a client open while you use its sandbox handles. Call box.close() when finished, or use with LithosBox() as box:. Closing the client does not delete its sandboxes. For asynchronous code, use AsyncLithosBox, async with, and await:

Create and find sandboxes

create

box.sandboxes.create() Returns a Sandbox. Choose at most one of image, template, or snapshot_id. Omit all three to use the default environment. Template and snapshot creates use the source’s CPU and memory configuration. The SDK reuses the same sandbox ID during retries within a create call. If a call fails without a response, inspect existing sandboxes before making a new create call. A handle exposes sandbox.id and sandbox.info, including its state, image, CPU, and memory configuration. sandbox.info.datasets lists the attached datasets in order (an empty list when there are none); sandbox.info.dataset is the first, or an empty string. sandbox.info.allow_egress lists the sandbox’s allowed destinations (an empty list when it has none). sandbox.info.timeout_s and sandbox.info.expires_unix_ns report its lifetime and when it ends (both 0 when it has none). with box.sandboxes.create() as sandbox: deletes the sandbox when the block ends.

Commands, sessions, and files

run

sandbox.run(command, *, background=False, stdin=None, stream=False, timeout=None) Returns an ExecResult with stdout, stderr, and exit_code. Call .check() to raise on a nonzero exit code. stdout_truncated, stderr_truncated, and truncated indicate output limits were reached. With background=True, the result reports startup status. No process ID is returned. Redirect output to a file and check the job’s eventual exit status separately. Foreground completion does not guarantee every child process has exited. Use background mode explicitly for servers and workers. Synchronous commands allow about 110 seconds; each output stream is limited to 16 MiB. Sandboxes below 1 GiB RAM can truncate earlier, as can text that expands when JSON-encoded. Check the truncation flags before using the result. See Run commands. With stream=True, iterate the returned ExecStream (async for on AsyncSandbox). Each ExecEvent has a kind: output (fd is stdout or stderr, data is raw bytes, seq and ts order it), gap (output the sandbox dropped before this reader asked for it, seq through gap_to), and finally exit (exit_code, reason: exited, killed, timeout, oom, sandbox_gone or lost). The stream reconnects on its own. stream.kill() stops the command; stream.exec_id and stream.next_seq let sandbox.exec(exec_id=..., from_seq=..., stream=True) resume it elsewhere. sandbox.exec(args, stream=True) is the argument-list form.

session

sandbox.session(session_id=None) Returns a Session that keeps its working directory, environment variables, and shell state between commands. Use with sandbox.session() as session: to close the shell when the block ends. In the async client, session() is the one factory you do not await — create() is awaited, session() is not, and the session’s own calls are:
Handles using the same session ID share that shell; closing one ends it for all handles. An individual session command has the same timeout as sandbox.run(). A sandbox supports up to 256 concurrent sessions. See Keep shell state for an example.

files

sandbox.files Writes allow up to 4 MiB per call. Reads allow approximately 16 MiB of text or 12 MiB of binary data. Use larger-file transfers for files beyond those limits. write() does not append.

Public endpoints

sandbox.expose(port, public_port=None) returns IngressInfo. Its id identifies the endpoint and its url is the public HTTPS address. Use the assigned public port by leaving public_port unset. Start an HTTP server on the specified sandbox port before using the URL. Remove an endpoint with box.ingress.delete(endpoint.id). List endpoints with sandbox.ingress() (SDK 0.6.9 and later), in the console, or with GET /vms/{id}/ingress. See Expose a web service for a complete example.

Outbound network

set_egress

sandbox.set_egress(*, disable_internet=False, allow_egress=None) Replaces the sandbox’s outbound network policy and returns the refreshed handle. The arguments are the whole policy; nothing is merged with the previous one. Connections the new policy no longer allows are closed. An archived sandbox applies the policy when it is unarchived. Entries follow the same rules as create(allow_egress=...); an invalid entry raises InvalidRequestError naming it. SDK 0.10.0 and later. See Limit outbound network access.

Lifecycle and checkpoints

See Manage sandbox lifecycle for when to use each action.

snapshot

sandbox.snapshot(*, leave_asleep=False, wait_durable=False, timeout=120) Returns SnapshotInfo, including id, vm_id, state, and durable. The sandbox keeps running by default. leave_asleep=True lets it rest after the checkpoint; the next command or file operation wakes it automatically. Use wait_durable=True to wait until saving completes, with timeout in seconds. A timeout does not cancel saving. Restore with box.snapshots.restore(snapshot); it accepts either a snapshot object or its ID and returns a new Sandbox.

fork

sandbox.fork(n=None, *, timeout_s=None) Without n, return one independent Sandbox. With n, return a list of that many copies; n=1 returns a one-element list. Up to 16 copies can be requested at once. timeout_s gives each copy its own lifetime; a copy never takes its parent’s (SDK 0.10.2 and later). Copies include files and running processes. Fork between commands and reconnect external services independently in each copy. See Save, restore, fork.

diff

box.snapshots.diff(a, b, *, content=False) Returns what changed from snapshot a to snapshot b (snapshot objects or IDs). It is read from the two snapshots, so nothing runs in either sandbox and either may already be deleted. Use any two of your snapshots: before and after a step, two forks, or two different sandboxes. With content=True, modified files also carry content: unified-diff hunks for text, changed_ranges for binary files. A diff usually takes under 3 seconds. A 409 saying a snapshot must finish uploading means it is not yet reachable where the diff runs; retry in a few seconds. A diff that has to fetch more than about 2 GiB of changed disk from storage (the two snapshots on different hosts, or on none) can run past the 120-second request limit; diff nearer snapshots.

merge

sandbox.merge(source, base, *, on_conflict="fail", dry_run=False) Applies to this running sandbox the file changes that turned snapshot base into snapshot source, compared three ways with the sandbox’s own changes since base. It merges files: contents, directories, symlinks, mode, and owner. It does not merge memory or processes. A path changed differently on both sides is a conflict; files merge whole, and a SQLite database is one file. on_conflict="fail" applies nothing if there is any conflict, "skip" applies everything else, and "theirs" takes the source’s version. dry_run=True returns the plan and changes nothing. The sandbox is snapshotted before anything changes. When something was applied, the report’s pre_merge_snapshot_id restores the sandbox to undo the merge. A merge writes at most 256 MiB of file content; a larger one returns 409. Hard links arrive as separate copies, modification times become the time of the merge, and extended attributes and sparse holes are not kept. See Save, restore, fork.

Activity, logs, and metrics

Recent metrics cover approximately two hours at 30-second intervals. Historical metrics use minute intervals and remain available for archived and deleted sandboxes. measured=false identifies a missing measurement. System logs and recent metrics are unavailable while archived. Recent CPU samples report milliseconds used over interval_s. Divide cpu_ms by interval_s * 1000 * cpus for a fraction of allocated CPU capacity. Historical points use util_seconds for the measured duration; do not assume every point contains a full minute of measurements. See Monitor and troubleshoot for examples.

Templates, registries, and exports

Registry credentials can also use secret= or ECR role settings: kind="ecr-assume-role", role_arn, and external_id. Export file links are temporary; download before their expiry. See HTTP API for export fields. Existing aliases include exec, exec_background, stop/start, branch, and files.ls. The guides use run, archive/unarchive, fork, and files.list.

Errors

OrganizationFrozenError and the cpu_cap, ram_cap, and storage_cap kinds require SDK 0.7.1 or later. Earlier versions raise a plain LithosBoxError with status 402 for a frozen organization and report every 403 quota as kind="cap". SandboxStoppedError subclasses ConflictError; SandboxUnhealthyError subclasses ServerError, so a broad except ServerError catches it. Error classes are available from lithosbox.errors. API errors expose status and body where a response is available. SandboxStoppedError is a subclass of ConflictError and the same class as SandboxArchivedError. box.rate.acquire and box.rate.ops expose rate-limit information from the client’s recent responses. They may be None before the first response. Treat the values as advisory and follow retry_after on rate-limit errors. See Limits and HTTP status codes.