Sandboxes
solari-sandbox: classes SandboxClient and Sandbox. A sandbox is a headless microVM for commands, code, files, and git. See the Python SDK hub for install and configuration.
pip install solari-sandboxSandbox method below is inherited from SessionHandle and is also available on Desktop. A Desktop is a Sandbox plus the GUI surface.Contents
SandboxClient: SandboxClient(), create(), create_desktop(), connect(), get(), list(), list_all(), kill(), list_snapshots(), get_snapshot(), delete_snapshot(), promote_snapshot(), aclose(), volumes.create(), volumes.list(), volumes.get(), volumes.delete()Sandbox: connect(), reconnect(), close(), commands.run(), commands.start(), pty.create(), run_code(), create_code_context(), files.*, git.*, env(), download_url(), upload_url(), volumes.mount(), metrics(), snapshot(), revert(), pause(), resume(), set_timeout(), preview_url(), kill()- Types:
CreateSandboxResponse,SandboxView,CommandResult,_CommandHandle,_PtyHandle,RunCodeResult,CodeResultItem,Chart,FsEntry,FsStat,FsSearchMatch,FsWatchEvent,GitStatus,GitBranch,GitCommit,MetricsResult,SnapshotView, errors
mem_mb, from_snapshot), but the dataclasses they return mirror the wire and stay camelCase: result.exitCode, view.sandboxId, stat.modTimeMs.SandboxClient
The sandbox entry point. Talks the gateway HTTP API.
Properties
volumesVolumeClient: persistent volume CRUD. Attach one at create time viacreate(volumes=...).
Constructors
SandboxClient()
SandboxClient(
*,
api_key: str,
base_url: str,
http: httpx.AsyncClient | None = None,
call_timeout_ms: int | None = None,
kind: "sandbox" | "desktop" = "sandbox",
) -> SandboxClientCreates a client. Does not open any connection. All arguments are keyword-only.
Parameters:
api_keystr: required.base_urlstr: required, e.g.https://api.getsolari.com.httphttpx.AsyncClient | None: reuse an existing client.call_timeout_msint | None: per-RPC timeout for handles this client creates. Default300_000.kind"sandbox" | "desktop": the flavourcreate()makes. Default"sandbox".
Returns: SandboxClient
Raises: SolariError if api_key or base_url is missing.
Example:
from solari_sandbox import SandboxClient
client = SandboxClient(
api_key="slr_live_...",
base_url="https://api.getsolari.com",
)SyncSandboxClient takes the same arguments (minus http) and drives a private event loop. Its list_all() returns a plain list rather than an async generator; the Sandbox handle is still async.Methods
create()
async create(
*,
template: str | None = None,
cpu: int | None = None,
mem_mb: int | None = None,
disk_gb: int | None = None,
envs: dict[str, str] | None = None,
metadata: dict[str, str] | None = None,
timeout_ms: int | None = None,
from_snapshot: str | None = None,
lifecycle: dict | None = None,
volumes: list[dict[str, str]] | None = None,
) -> SandboxCreates a sandbox and returns a live handle. The call sends an idempotency key, so retrying is safe.
Parameters:
templatestr | None: e.g."base","code","python-kernel", or a promoted template id.cpu,mem_mb,disk_gbint | None: vCPUs, memory in MiB, disk in GiB. The host clamps to slot capacity.envsdict[str, str] | None: per-session env.metadatadict[str, str] | None: opaque labels, filterable on list().timeout_msint | None: idle window before auto-release.from_snapshotstr | None: boot from a snapshot instead of the template.lifecycledict | None: idle policy with camelCase wire keys, e.g.{"onTimeout": "pause", "autoResume": True}.volumeslist[dict[str, str]] | None: each{"volumeId": ..., "path": ...}, mounted before the session starts.
Returns: Sandbox
Raises: AuthError, PlanError, ConcurrencyLimitError, NoCapacityError, or GatewayError.
Example:
sbx = await client.create(
template="base",
cpu=2,
mem_mb=4096,
envs={"NODE_ENV": "test"},
)create_desktop()
async create_desktop(
*,
template: str | None = None,
cpu: int | None = None,
mem_mb: int | None = None,
disk_gb: int | None = None,
envs: dict[str, str] | None = None,
metadata: dict[str, str] | None = None,
timeout_ms: int | None = None,
from_snapshot: str | None = None,
resolution: str | None = None,
record: bool | None = None,
lifecycle: dict | None = None,
volumes: list[dict[str, str]] | None = None,
) -> DesktopCreates a GUI session over the same /sandboxes route and returns a Desktop handle. Adds resolution and record to the sandbox options.
Parameters:
- All of create(), plus
resolutionstr | None andrecordbool | None.
Returns: Desktop
Example:
vm = await client.create_desktop(template="office", resolution="1280x720")connect()
async connect(sandbox_id: str) -> SandboxRe-attaches to a running sandbox by id.
Parameters:
sandbox_idstr: the sandbox id.
Returns: Sandbox
Raises: GatewayError if the id is unknown.
Example:
sbx = await client.connect("sbx_abc123")
await sbx.commands.run("uptime")DesktopClient.connect(), this only rebuilds a handle. Call resume() on it if the sandbox is paused.get()
async get(sandbox_id: str) -> SandboxViewFetches a sandbox’s current record: state, kind, metadata, resources. Does not return a handle.
Parameters:
sandbox_idstr: the sandbox id.
Returns: SandboxView
Example:
view = await client.get("sbx_abc123")
print(view.state) # "running" | "paused" | ...list()
async list(
*,
metadata: dict[str, str] | None = None,
state: SandboxState | None = None,
kind: SandboxKind | None = None,
limit: int | None = None,
cursor: str | None = None,
) -> dictLists sandboxes with optional filters. One page. Follow nextCursor, or use list_all().
Parameters:
metadatadict[str, str] | None: match on caller-set metadata.stateSandboxState | None:starting,running,paused,archived,releasing,gone.kindSandboxKind | None:sandboxordesktop.limitint | None,cursorstr | None: page size and cursor.
Returns: dict, a plain dict, not a dataclass: {"sandboxes": list[SandboxView], "nextCursor": str | None}.
Example:
page = await client.list(state="running", limit=50)
print(len(page["sandboxes"]), page["nextCursor"])list_all()
list_all(
*,
metadata: dict[str, str] | None = None,
state: SandboxState | None = None,
kind: SandboxKind | None = None,
limit: int | None = None,
) -> AsyncIterator[SandboxView]Auto-paginates list(), following nextCursor until exhausted. An async generator. Iterate it, do not await it.
Parameters:
- Same filters as
list(), minuscursor.
Returns: AsyncIterator[SandboxView]
Example:
async for s in client.list_all(state="running"):
print(s.sandboxId, s.cpu, s.memMb)kill()
async kill(sandbox_id: str) -> NoneDestroys a sandbox. Idempotent.
Parameters:
sandbox_idstr: the sandbox id.
Returns: None
Example:
await client.kill("sbx_abc123")list_snapshots()
async list_snapshots(
*,
template: str | None = None,
kind: SandboxKind | None = None,
limit: int | None = None,
) -> list[SnapshotView]Lists snapshots, optionally filtered by template or kind.
Parameters:
templatestr | None,kindSandboxKind | None,limitint | None.
Returns: list[SnapshotView], the list directly, not wrapped in a dict.
Example:
for snap in await client.list_snapshots(template="base"):
print(snap.id, snap.sizeBytes)get_snapshot()
async get_snapshot(snapshot_id: str) -> SnapshotViewFetches one snapshot record.
Parameters:
snapshot_idstr: e.g.snap_abc123.
Returns: SnapshotView
Example:
snap = await client.get_snapshot("snap_abc123")
print(snap.sizeBytes, snap.parent)delete_snapshot()
async delete_snapshot(snapshot_id: str) -> NoneDeletes a snapshot.
Parameters:
snapshot_idstr: the snapshot id.
Returns: None
Raises: GatewayError if the snapshot has live children.
Example:
await client.delete_snapshot("snap_abc123")promote_snapshot()
async promote_snapshot(snapshot_id: str, name: str) -> dictPromotes a snapshot to a reusable template you can pass as template on future creates.
Parameters:
snapshot_idstr: the snapshot id.namestr: template name.
Returns: dict, the raw body, with templateId and name.
Example:
snap_id = await sbx.snapshot("deps-installed")
tpl = await client.promote_snapshot(snap_id, "my-stack")
fresh = await client.create(template=tpl["templateId"])aclose()
async aclose() -> NoneCloses the underlying HTTP clients, if this client owns them. Does not touch live sandboxes.
Returns: None
Example:
await client.aclose()volumes.create()
async volumes.create(
*,
name: str | None = None,
size_mb: int | None = None,
metadata: dict[str, str] | None = None,
) -> dictCreates a persistent, S3-backed volume your org owns. It outlives any session.
Parameters:
namestr | None: server default"volume".size_mbint | None: advisory hint; storage is elastic, not a hard cap.metadatadict[str, str] | None: opaque labels.
Returns: dict, the raw volume body, with volumeId and name. The volume calls return plain dicts, not dataclasses.
Example:
vol = await client.volumes.create(name="datasets")
sbx = await client.create(
volumes=[{"volumeId": vol["volumeId"], "path": "/data"}],
)volumes.list()
async volumes.list() -> list[dict]Lists the org’s volumes, newest first.
Returns: list[dict]
Example:
for v in await client.volumes.list():
print(v["volumeId"], v["name"])volumes.get()
async volumes.get(volume_id: str) -> dictFetches one volume’s metadata.
Parameters:
volume_idstr: e.g.vol_abc123.
Returns: dict
Example:
vol = await client.volumes.get("vol_abc123")volumes.delete()
async volumes.delete(volume_id: str) -> NoneDeletes a volume’s metadata. Idempotent.
Parameters:
volume_idstr: the volume id.
Returns: None
Example:
await client.volumes.delete("vol_abc123")Sandbox
A live headless session. Construct via SandboxClient.create().
Properties
idstr: the session id.sandboxIdstr: alias ofid.controlUrlstr:wss://JSON-RPC control channel.expiresAtstr: ISO 8601 expiry.connectedbool: whether the control channel is open.commands,pty,files,volumes,git: the grouped namespaces below.
Channel
connect()
async connect() -> NoneOpens the control WebSocket. Idempotent, but required: call it once after create() (or connect()) before any command, code, file, or git call. Those methods do not open the channel themselves and raise ConnectionError ("Not connected") until it is open.
Returns: None
Example:
await sbx.connect()reconnect()
async reconnect() -> NoneRe-opens the control socket after a drop. commands.connect() is an alias of this.
Returns: None
Example:
if not sbx.connected:
await sbx.reconnect()close()
async close() -> NoneCloses the control channel locally. Does not release the remote session. Use kill() for that. Unlike the TypeScript SDK this is a coroutine and must be awaited.
Returns: None
Example:
await sbx.close() # session keeps running until its timeoutCommands
commands.run()
async commands.run(
cmd: str,
*,
args: list[str] | None = None,
cwd: str | None = None,
env: dict[str, str] | None = None,
user: str | None = None,
timeout_ms: int | None = None,
background: bool = False,
on_stdout: Callable[[str], None] | None = None,
on_stderr: Callable[[str], None] | None = None,
) -> CommandResultRuns a command to completion and returns its exit code and captured output.
Parameters:
cmdstr: the program to run; the only positional argument.argslist[str] | None: arguments; no shell expansion.cwd,userstr | None,envdict[str, str] | None: per-command context.backgroundbool: return immediately with an empty result; output still reaches the callbacks.on_stdout,on_stderrCallable[[str], None] | None: streamed output.
Returns: CommandResult: exitCode, stdout, stderr. A non-zero exit returns normally; it does not raise.
Example:
r = await sbx.commands.run("ls", args=["-la", "/tmp"])
print(r.exitCode, r.stdout)
# stream output as it arrives
import sys
await sbx.commands.run(
"npm",
args=["install"],
cwd="/app",
on_stdout=sys.stdout.write,
)cmd is executed directly with args, not through a shell. Pipes, globs, and && will not expand. For shell syntax use run("sh", args=["-c", "…"]).call_timeout_ms. Enforce a deadline yourself with asyncio.wait_for if you need one.commands.start()
async commands.start(
cmd: str,
*,
args: list[str] | None = None,
cwd: str | None = None,
env: dict[str, str] | None = None,
user: str | None = None,
on_stdout: Callable[[str], None] | None = None,
on_stderr: Callable[[str], None] | None = None,
) -> _CommandHandleStarts a command and returns a handle immediately, without waiting for exit. Use it for interactive or long-running processes.
Parameters:
cmdstr: the program to run.- Others as commands.run(); there is no
timeout_msorbackground.
Returns: _CommandHandle: cmdId, stdin(), on_data(), wait(), kill().
Example:
proc = await sbx.commands.start("python3", args=["-u", "worker.py"])
proc.on_data(lambda stream, data: print(stream, data))
await proc.stdin("input\n")
exit_code = await proc.wait()pty.create()
async pty.create(
*,
cols: int,
rows: int,
cmd: str | None = None,
cwd: str | None = None,
env: dict[str, str] | None = None,
) -> _PtyHandleOpens a pseudo-terminal, a real TTY, so interactive programs and curses UIs behave normally.
Parameters:
cols,rowsint: required terminal size; keyword-only.cmdstr | None: program to run; defaults to the guest shell.cwdstr | None,envdict[str, str] | None.
Returns: _PtyHandle: ptyId, write(), resize(), on_data(), kill().
Example:
pty = await sbx.pty.create(cols=80, rows=24)
pty.on_data(lambda b: print(b.decode("utf-8", "replace"), end=""))
await pty.write("ls -la\n")
await pty.kill()Code
run_code()
async run_code(
code: str,
*,
language: CodeLanguage | None = None,
context_id: str | None = None,
on_stdout: Callable[[str], None] | None = None,
on_stderr: Callable[[str], None] | None = None,
) -> RunCodeResultRuns code in a stateful kernel. Rich outputs (PNG, HTML, JSON, structured charts) come back as results items.
Parameters:
codestr: source to execute.languageCodeLanguage | None:python,javascript,typescript,bash,r. The guest defaults topython.context_idstr | None: kernel context to reuse; see create_code_context().on_stdout,on_stderrCallable[[str], None] | None: invoked per matching result item.
Returns: RunCodeResult: results, charts, error. A runtime error in the code lands in error; it does not raise.
Example:
r = await sbx.run_code("""
import matplotlib.pyplot as plt
plt.plot([1, 2, 3], [4, 5, 6])
plt.title("demo")
plt.show()
""")
print(r.charts[0].type) # "line"
print(r.charts[0].title) # "demo"
png = next((i.png for i in r.results if i.png), None) # base64results by stream, not a live feed.create_code_context()
async create_code_context(language: CodeLanguage = "python") -> strCreates a fresh kernel context so state persists across run_code() calls, like a REPL.
Parameters:
languageCodeLanguage: default"python".
Returns: str, the context id.
Example:
ctx = await sbx.create_code_context("python")
await sbx.run_code("x = 41", context_id=ctx)
r = await sbx.run_code("print(x + 1)", context_id=ctx) # 42Files
files.* moves bytes over the control channel. For large files prefer download_url() / upload_url().
files.read() / files.read_text()
async files.read(path: str) -> bytes
async files.read_text(path: str) -> strReads a file’s bytes, or its UTF-8 decoded text.
Parameters:
pathstr: absolute in-guest path.
Returns: bytes / str
Example:
text = await sbx.files.read_text("/etc/hostname")
data = await sbx.files.read("/tmp/out.bin")files.write()
async files.write(path: str, data: bytes | str, mode: int | None = None) -> NoneWrites a file, creating or truncating it.
Parameters:
pathstr: absolute in-guest path.databytes | str: content;stris UTF-8 encoded.modeint | None: permission bits, e.g.0o755.
Returns: None
Example:
await sbx.files.write("/app/run.sh", "#!/bin/sh\necho hi\n", 0o755)files.list()
async files.list(path: str) -> list[FsEntry]Lists a directory’s entries (non-recursive).
Parameters:
pathstr: directory path.
Returns: list[FsEntry], each with name, dir, size.
Example:
for e in await sbx.files.list("/app"):
print("d" if e.dir else "-", e.name, e.size)files.stat()
async files.stat(path: str) -> FsStatReturns one entry’s metadata.
Parameters:
pathstr: file or directory path.
Returns: FsStat: name, dir, size, mode, modTimeMs.
Example:
st = await sbx.files.stat("/app/run.sh")
print(st.size, oct(st.mode), st.modTimeMs)files.rename()
async files.rename(frm: str, to: str) -> NoneRenames or moves a path. The first parameter is spelled frm, because from is a Python keyword.
Parameters:
frm,tostr: source and destination paths.
Returns: None
Example:
await sbx.files.rename("/tmp/a.txt", "/tmp/b.txt")files.remove()
async files.remove(path: str, recursive: bool = False) -> NoneDeletes a file or directory.
Parameters:
pathstr: path to delete.recursivebool: defaultFalse; required for non-empty directories.
Returns: None
Example:
await sbx.files.remove("/tmp/build", True)files.mkdir()
async files.mkdir(path: str) -> NoneCreates a directory.
Parameters:
pathstr: directory to create.
Returns: None
Example:
await sbx.files.mkdir("/app/data")files.search()
async files.search(path: str, query: str, max_results: int | None = None) -> list[FsSearchMatch]Recursively greps for query under path.
Parameters:
pathstr: root to search.querystr: the pattern.max_resultsint | None: cap on matches.
Returns: list[FsSearchMatch], each with path, line, text.
Example:
for m in await sbx.files.search("/app", "TODO", 20):
print(f"{m.path}:{m.line}: {m.text}")files.watch()
async files.watch(
path: str,
cb: Callable[[FsWatchEvent], None],
recursive: bool = False,
) -> Callable[[], Awaitable[None]]Watches a path for changes. Returns an async unwatch callable. Await it to stop.
Parameters:
pathstr: path to watch.cbCallable[[FsWatchEvent], None]: invoked per event withtypeandpath.recursivebool: defaultFalse.
Returns: Callable[[], Awaitable[None]]: the unwatch function.
Example:
unwatch = await sbx.files.watch(
"/app",
lambda ev: print(ev.type, ev.path),
True,
)
await unwatch()files.upload() / files.download()
async files.upload(path: str, data: bytes | str) -> None
async files.download(path: str) -> bytesTransfer a file over the control channel. Same bytes as write() / read().
Parameters:
pathstr: in-guest path.databytes | str: upload content.
Returns: None / bytes
Example:
await sbx.files.upload("/app/data.csv", csv_string)
out = await sbx.files.download("/app/result.json")download_url()
async download_url(path: str) -> dictSigned, time-limited URL to download an in-guest file directly over HTTP, bypassing the control channel. Use for large files.
Parameters:
pathstr: in-guest file path.
Returns: dict, with url and usually expiresAt.
Raises: RuntimeError if the handle was not created by a client.
Example:
import httpx
dl = await sbx.download_url("/app/big.tar.gz")
async with httpx.AsyncClient() as http:
res = await http.get(dl["url"])upload_url()
async upload_url(path: str | None = None) -> dictSigned, time-limited URL to upload a file into the guest directly over HTTP.
Parameters:
pathstr | None: destination path; optional, some backends return a prefix.
Returns: dict, with url and usually expiresAt.
Raises: RuntimeError if the handle was not created by a client.
Example:
up = await sbx.upload_url("/app/big.zip")
async with httpx.AsyncClient() as http:
await http.put(up["url"], content=data)Git
Every git.* call is a non-shell git invocation in the guest, so there is no injection surface. Requires git on PATH, which the base template ships.
git.clone()
async git.clone(
url: str,
*,
path: str | None = None,
branch: str | None = None,
depth: int | None = None,
username: str | None = None,
password: str | None = None,
cwd: str | None = None,
) -> NoneClones a repository into the guest.
Parameters:
urlstr: remote URL.pathstr | None: destination dir.branchstr | None: branch or tag.depthint | None: shallow-clone depth.username,passwordstr | None: basic auth for private HTTPS remotes; injected into the URL for this invocation only.cwdstr | None: working directory.
Returns: None
Raises: RuntimeError with git’s stderr on a non-zero exit.
Example:
await sbx.git.clone(
"https://github.com/org/repo.git",
path="/app/repo",
branch="main",
depth=1,
)git.status()
async git.status(cwd: str | None = None) -> GitStatusParsed working-tree status.
Parameters:
cwdstr | None: repository directory.
Returns: GitStatus
Raises: RuntimeError on a non-zero exit.
Example:
st = await sbx.git.status("/app/repo")
print(st.branch, st.clean, st.ahead, st.modified)git.add()
async git.add(paths: list[str], cwd: str | None = None) -> NoneStages paths. An empty list is a no-op.
Parameters:
pathslist[str]: use["."]for everything.cwdstr | None: repository directory.
Returns: None
Example:
await sbx.git.add(["."], "/app/repo")git.commit()
async git.commit(
message: str,
*,
cwd: str | None = None,
author: str | None = None,
email: str | None = None,
all: bool = False,
) -> dictCommits staged changes and returns the new hash. Set author/email, because an ephemeral session has no git identity.
Parameters:
messagestr: commit message.cwdstr | None: repository directory.author,emailstr | None: identity, scoped to this commit only.allbool: stage all tracked modifications first.
Returns: dict: {"hash": str}.
Raises: RuntimeError on a non-zero exit.
Example:
c = await sbx.git.commit(
"add feature",
cwd="/app/repo",
author="CI Bot",
email="ci@example.com",
)
print(c["hash"])git.push() / git.pull()
async git.push(
*,
cwd: str | None = None,
remote: str | None = None,
branch: str | None = None,
username: str | None = None,
password: str | None = None,
) -> None
async git.pull(...) # identical signaturePushes to or pulls from a remote. Credentials are used for this one invocation and never persisted to the repo config.
Parameters:
cwdstr | None: repository directory.remotestr | None: default"origin".branchstr | None: defaults to the current branch’s upstream.username,passwordstr | None: one-off HTTPS auth.
Returns: None
Raises: RuntimeError on a non-zero exit.
Example:
import os
await sbx.git.push(
cwd="/app/repo",
username="x-access-token",
password=os.environ["GITHUB_TOKEN"],
)git.checkout()
async git.checkout(ref: str, *, cwd: str | None = None, create: bool = False) -> NoneChecks out an existing ref, or creates a branch.
Parameters:
refstr: branch, tag, or commit.cwdstr | None: repository directory.createbool: create the branch (-b).
Returns: None
Example:
await sbx.git.checkout("feature/x", cwd="/app/repo", create=True)git.branches()
async git.branches(cwd: str | None = None) -> list[GitBranch]Lists local branches.
Parameters:
cwdstr | None: repository directory.
Returns: list[GitBranch], each with name, commit, current.
Example:
for b in await sbx.git.branches("/app/repo"):
print("*" if b.current else " ", b.name, b.commit)git.log()
async git.log(*, cwd: str | None = None, max_count: int | None = None) -> list[GitCommit]Recent commits, newest first.
Parameters:
cwdstr | None: repository directory.max_countint | None: cap the number returned.
Returns: list[GitCommit], each with hash, author, email, date, message.
Example:
commits = await sbx.git.log(cwd="/app/repo", max_count=10)Lifecycle & admin
metrics(), snapshot(), revert(), pause(), resume(), set_timeout(), download_url(), upload_url(), and preview_url() reach the gateway through hooks the client installs. On a hand-built handle they raise RuntimeError.env()
async env(vars: dict[str, str]) -> NoneInjects or replaces per-session environment variables in the guest.
Parameters:
varsdict[str, str]: the variables to set.
Returns: None
Example:
await sbx.env({"API_URL": "https://staging.example.com"})volumes.mount()
async volumes.mount(vol_id: str, path: str) -> NoneMounts a persistent volume into the running guest. Prefer attaching at create time via create(volumes=...).
Parameters:
vol_idstr: volume id.pathstr: absolute in-guest mount point.
Returns: None
Example:
await sbx.volumes.mount("vol_abc123", "/data")metrics()
async metrics() -> MetricsResultLive resource usage for this session.
Returns: MetricsResult: cpuPct, memBytes, memTotalBytes, diskBytes.
Raises: RuntimeError if the handle was not created by a client.
Example:
m = await sbx.metrics()
print(m.cpuPct, m.memBytes / m.memTotalBytes)snapshot()
async snapshot(name: str | None = None) -> strCheckpoints this running session and returns the snapshot id. The session keeps running.
Parameters:
namestr | None: optional label.
Returns: str, the snapshot id.
Raises: RuntimeError if the handle was not created by a client.
Example:
await sbx.commands.run("pip", args=["install", "pandas"])
snap_id = await sbx.snapshot("deps-installed")
# boot future sandboxes from it
fast = await client.create(from_snapshot=snap_id)revert()
async revert(snapshot_id: str) -> NoneRestores this session in place from a snapshot. The session id stays stable.
Parameters:
snapshot_idstr: snapshot to restore.
Returns: None
Raises: RuntimeError if the handle was not created by a client.
Example:
await sbx.revert(snap_id) # back to a known-good statepause()
async pause() -> NoneSaves full RAM + disk state and closes the control channel. Resume later with resume().
Returns: None
Raises: RuntimeError if the handle was not created by a client.
Example:
await sbx.pause() # stops billing for compute
await sbx.resume() # full state restoredresume()
async resume() -> NoneResumes a paused session and re-points the control channel at the fresh slot.
Returns: None
Raises: RuntimeError if the handle was not created by a client.
Example:
await sbx.resume()
r = await sbx.commands.run("cat", args=["/tmp/state"])set_timeout()
async set_timeout(timeout_ms: int) -> dictExtends the rolling idle window. Every use of the session resets it.
Parameters:
timeout_msint: new idle window in ms.
Returns: dict, the raw body, carrying expiresAt.
Raises: RuntimeError if the handle was not created by a client.
Example:
r = await sbx.set_timeout(30 * 60_000) # 30 min
print(r["expiresAt"])preview_url()
async preview_url(port: int) -> dictResolves a public URL for a port listening inside the guest, e.g. a dev server.
Parameters:
portint: the in-guest port.
Returns: dict, with url, and token when the gateway signs one.
Raises: RuntimeError if the handle was not created by a client.
Example:
await sbx.commands.start("python3", args=["-m", "http.server", "3000"])
p = await sbx.preview_url(3000)
print(p["url"])kill()
async kill() -> NoneDestroys the remote session and closes the channel. Idempotent.
Returns: None
Example:
try:
await sbx.commands.run("./build.sh")
finally:
await sbx.kill()Types
CreateSandboxResponse
sandboxIdstr,kindSandboxKind.controlUrlstr,expiresAtstr.streamUrlstr | None: present only whenkind == "desktop".
SandboxView
sandboxIdstr,kindSandboxKind,stateSandboxState.metadatadict,expiresAtstr.cpuint,memMbint.
CommandResult
exitCodeint,stdoutstr,stderrstr.
_CommandHandle
Returned by commands.start(); not constructed directly.
cmdIdstr: the command id.on_data(cb): subscribe to(stream, data)chunks. Synchronous. Chunks that arrive before the first subscriber are buffered and replayed, so early output is never lost.await stdin(data): write to the command’s stdin.await wait():int, the exit code. Raises if the control channel drops before the process exits.await kill(signal=None): signal the process.
_PtyHandle
ptyIdstr: the PTY id.on_data(cb): subscribe to rawbytes. Synchronous.await write(data),await resize(cols, rows),await kill().
RunCodeResult / CodeResultItem
resultslist[CodeResultItem]: each hastype(stdout/stderr/result) plus any oftext,png,jpeg,svg,html,latex,json,markdown,chart.chartslist[Chart]: every chart acrossresults, flattened.errorAny: the raw error object when the code raised.
Chart
typeChartType:line,scatter,bar,pie,box_and_whisker,composite,unknown.title,xLabel,yLabelstr | None.x,yChartAxis | None:label,ticks,scale.elementslist | None: per-type data (points, bars, slices).
FsEntry / FsStat / FsSearchMatch / FsWatchEvent
FsEntry:namestr,dirbool,sizeint.FsStat:FsEntry’s fields plusmodeint (permission bits) andmodTimeMsint (unix millis).FsSearchMatch:pathstr,lineint,textstr.FsWatchEvent:typestr,pathstr.
GitStatus / GitBranch / GitCommit
GitStatus.branchstr: empty on a detached HEAD.GitStatus.detached,cleanbool.GitStatus.ahead,behindint: relative to the upstream, when set.GitStatus.staged,modified,untrackedlist[str].GitBranch:name,commitstr,currentbool.GitCommit:hash,author,email,date,messagestr.
MetricsResult
cpuPctfloat,memBytesint,memTotalBytesint,diskBytesint.
SnapshotView
idstr,parentstr | None,namestr | None.sizeBytesint,createdAtstr,kindSandboxKind,templatestr.
Errors
All extend SolariError. Gateway errors also carry status, code, and body.
| Class | Status | Meaning |
|---|---|---|
AuthError | 401 | API key missing, malformed, or rejected. |
PlanError | 402 | The plan does not allow this. |
ConcurrencyLimitError | 429 | Too many live sessions. Never retried. |
NoCapacityError | 503 | No host available right now. |
GatewayError | other | Any other non-2xx response. |
ActionError | None | An RPC returned ok: false. Carries method. |
TimeoutError | None | No reply within the per-call timeout. Carries method, timeoutMs. |
ConnectionError | None | The control channel is not open, or a transport failure. |
from solari_sandbox import ConcurrencyLimitError, NoCapacityError
try:
sbx = await client.create(template="base")
except NoCapacityError:
await retry_later()
except ConcurrencyLimitError:
await wait_for_slot()