Python client for VoidRun AI sandboxes: run commands, manage files, use pseudo-terminals, stream output, and execute multi-language code in isolated environments.
The high-level API is aligned with the official TypeScript SDK (create_sandbox, list_sandboxes, remove_sandbox, Sandbox.run_code, CodeExecutionResult, and shared defaults).
- Features
- Requirements
- Installation
- Configuration
- Quick start
- Parity with the TypeScript SDK
- Core concepts
- Code execution and interpreter
- Background commands
- File system
- File watching (async)
- Pseudo-terminal (PTY)
- API reference (summary)
- Runnable examples
- Development and tests
- Building and publishing
- Error handling
- Troubleshooting
- Contributing, license, support
- Sandbox lifecycle: Create, list, fetch, start/stop/pause/resume, and remove sandboxes (sync and async).
- Shell execution: synchronous
execwith optionalExecRequest(nobackgroundflag); SSE streaming viaexec_stream; detached processes via.commands.run. - Code interpreter:
run_code/interpreter.runreturn a structuredCodeExecutionResult(stdout, stderr, success, exit_code, results, logs). - Files: Create, upload, download, list, move, copy, compress, extract, permissions, search, disk usage.
- File watching: WebSocket-backed directory watches (async).
- PTY: Ephemeral and persistent terminal sessions, resize,
run_commandwith prompt detection. - Background commands: Run, list, attach, wait, kill long-running processes.
- Python 3.9+
- Dependencies: see
pyproject.toml(Pydantic v2, httpx, urllib3, websockets, python-dotenv, etc.).
pip install voidrunWith Poetry:
poetry add voidrunFrom this repository (editable install for development):
cd py-sdk
pip install -e .VoidRun and AsyncVoidRun require an API key. The API base URL defaults to the hosted endpoint (DEFAULT_API_BASE_URL, aligned with the TypeScript SDK and OpenAPI servers). Set VR_API_URL or API_URL, or pass base_url=, only for self-hosted deployments.
from voidrun import VoidRun
vr = VoidRun(
api_key="your-api-key", # optional if VR_API_KEY / API_KEY is set
)| Variable | Purpose |
|---|---|
VR_API_KEY or API_KEY |
API key (required unless passed to the constructor). |
VR_API_URL or API_URL |
(Self-hosted only.) Overrides the default API base URL. |
Exported from voidrun:
| Constant | Value | Meaning |
|---|---|---|
DEFAULT_API_BASE_URL |
https://platform.void-run.com/api |
Default API host when VR_API_URL / API_URL are unset. |
DEFAULT_SANDBOX_IMAGE |
"code" |
Default image id when creating a sandbox without image=. |
DEFAULT_SANDBOX_CPU |
1 |
Default CPU count. |
DEFAULT_SANDBOX_MEM |
1024 |
Default memory in MB. |
For self-hosted VoidRun, set VR_API_URL (or API_URL) to your instance’s API root (including /api if that is how your server is mounted).
from voidrun import VoidRun
vr = VoidRun() # uses VR_API_KEY; default base URL unless VR_API_URL / API_URL is set
sandbox = vr.create_sandbox(mem=1024, cpu=1)
result = sandbox.exec('echo "Hello from VoidRun"')
# Exec returns VoidRunResponse whose .data is ExecResponseData
print(result.data.stdout)
sandbox.remove()import asyncio
from voidrun import AsyncVoidRun
async def main():
async with AsyncVoidRun() as vr:
sandbox = await vr.create_sandbox(mem=1024, cpu=1)
result = sandbox.exec('echo "Hello"')
print(result.data.stdout)
await sandbox.remove_async()
asyncio.run(main())Sync: exiting the block calls remove(). Async: __aexit__ calls remove_async().
with vr.create_sandbox() as sandbox:
print(sandbox.id)async with await vr.create_sandbox() as sandbox:
print(sandbox.id)Use the same mental model as @voidrun/sdk (or the internal TS client):
| TypeScript (concept) | Python |
|---|---|
createSandbox |
VoidRun.create_sandbox / await AsyncVoidRun.create_sandbox |
getSandbox |
get_sandbox |
listSandboxes |
list_sandboxes → ListSandboxesResult |
removeSandbox |
remove_sandbox or sandbox.remove() |
sandbox.runCode |
sandbox.run_code |
CodeExecutionResult |
CodeExecutionResult (Pydantic model) |
CodeInterpreter |
CodeInterpreter (alias: Interpreter on sandbox.interpreter) |
Listing sandboxes returns a ListSandboxesResult with:
.sandboxes: list ofSandboxinstances.meta:ListSandboxesMeta(total,page,limit,total_pages)
Recommended methods
create_sandbox(...)→Sandboxget_sandbox(sandbox_id)→Sandboxlist_sandboxes(page=..., limit=...)→ListSandboxesResultremove_sandbox(sandbox_id)→None
AsyncVoidRun exposes the same names with await and provides aclose() (and async with support) to shut down the thread pool used for API calls.
Keyword aliases
Create options accept both snake_case and camelCase where noted in code (e.g. env_vars / envVars, org_id / orgId).
Notable attributes: id, name, cpu, mem, org_id, status, env_vars, image, region, ref_id, auto_sleep, disk_mb, created_at, created_by.
Sub-clients:
.fs: file operations.pty: pseudo-terminal.interpreter:CodeInterpreter.commands: background processes
Lifecycle:
start,stop,pause,resume(and*_asyncvariants where applicable)remove()/remove_async(): delete sandbox on the APIdelete()/delete_async(): deprecated aliases forremove
info() returns self (same idea as TS sandbox.info()).
Accepts a command string, or keyword command=, or a full ExecRequest:
from voidrun.api_client.models.exec_request import ExecRequest
r = sandbox.exec("uname -a")
r = sandbox.exec(command="pwd", cwd="/tmp", timeout=60)
r = sandbox.exec(ExecRequest(command="ls", cwd="/"))Return type: VoidRunResponse[ExecResponseData]: the API’s outer ExecResponse envelope is unwrapped so r.data is ExecResponseData (stdout / stderr / exit_code):
print(r.data.stdout, r.data.stderr, r.data.exit_code)sandbox.exec is synchronous only — it waits for the command. Do not use a background flag on ExecRequest; for a PID and long-running processes, use sandbox.commands.run (Background commands).
Provide callbacks for SSE events (on_stdout, on_stderr, on_exit, on_error).
sandbox.interpreter is a CodeInterpreter. Both interpreter.run(...) and sandbox.run_code(...) return CodeExecutionResult directly (no .data nesting):
result = sandbox.run_code("print(2 + 2)", language="python")
print(result.stdout.strip()) # "4"
print(result.success)
result = sandbox.interpreter.run("console.log(1+1)", language="javascript")Supported languages (typical): python, javascript, typescript, node, bash, sh (as supported by your VoidRun deployment).
run_result = sandbox.commands.run(
"sleep 5 && echo done",
{"DEBUG": "true"},
"/tmp",
0,
)
print(run_result.data.pid)
sandbox.commands.list()
sandbox.commands.connect(pid, on_stdout=..., on_stderr=..., on_exit=...)
sandbox.commands.wait(pid)
sandbox.commands.kill(pid)Response shapes follow the generated OpenAPI models; access fields via .data on VoidRunResponse where applicable.
sandbox.fs.create_file("/tmp/hello.txt")
sandbox.fs.upload_file("/tmp/hello.txt", "Hello, World!")
sandbox.fs.upload_file_from_path("/tmp/remote.txt", "/local/file.txt")
data = sandbox.fs.download_file("/tmp/hello.txt")
sandbox.fs.delete_file("/tmp/hello.txt")
result = sandbox.fs.list_files("/tmp")
files = result.data
sandbox.fs.stat_file("/tmp/hello.txt")
sandbox.fs.create_directory("/tmp/mydir")
sandbox.fs.move_file("/tmp/a.txt", "/tmp/b.txt")
sandbox.fs.copy_file("/tmp/a.txt", "/tmp/copy.txt")
sandbox.fs.change_permissions("/tmp/a.txt", "755")
sandbox.fs.head_tail("/tmp/log.txt", head=True, lines=10)
sandbox.fs.search_files("/tmp", "*.txt")
sandbox.fs.disk_usage("/tmp")
archive = sandbox.fs.compress_file("/tmp", "tar.gz")
sandbox.fs.extract_archive("/tmp/archive.tar.gz", "/tmp/extracted")import asyncio
from voidrun import AsyncVoidRun
async def watch_tmp():
async with AsyncVoidRun() as vr:
sandbox = await vr.create_sandbox()
watcher = await sandbox.fs.watch(
"/app",
recursive=True,
on_event=lambda evt: print(evt.get("path"), evt.get("type")),
on_error=lambda err: print("watch error:", err),
)
await asyncio.sleep(60)
watcher.close()
await sandbox.remove_async()
asyncio.run(watch_tmp())import asyncio
from voidrun import AsyncVoidRun
async def ephemeral():
async with AsyncVoidRun() as vr:
sandbox = await vr.create_sandbox()
pty = await sandbox.pty.connect(
on_data=lambda data: print(data, end=""),
on_error=lambda err: print("PTY error:", err),
)
pty.send_input('echo "Hello"\n')
await asyncio.sleep(2)
await pty.close()
await sandbox.remove_async()
asyncio.run(ephemeral())async def persistent():
async with AsyncVoidRun() as vr:
sandbox = await vr.create_sandbox()
response = sandbox.pty.create_session()
session_id = response.data.data.session_id
pty = await sandbox.pty.connect(
session_id=session_id,
on_data=lambda data: print(data, end=""),
)
pty.send_input('echo "Hello"\n')
await pty.close()
reconnected = await sandbox.pty.connect(
session_id=session_id,
on_data=lambda data: print(data, end=""),
)
await reconnected.close()
sandbox.pty.delete_session(session_id)
await sandbox.remove_async()# After connect(...)
output = await pty.run_command("ls -la", timeout=5000, prompt="# ")
await pty.resize(80, 24)
sandbox.pty.list()
sandbox.pty.delete_session(session_id)VoidRun, AsyncVoidRun, Sandbox, CodeInterpreter, Interpreter (alias), CodeExecutionResult, ListSandboxesResult, ListSandboxesMeta, DEFAULT_SANDBOX_*.
| Field | Type | Description |
|---|---|---|
success |
bool |
Derived from exit code. |
stdout / stderr |
str |
Combined streams. |
exit_code |
int | None |
Process exit code. |
results |
Any |
Parsed output when applicable. |
error |
str | None |
Error hint (often stderr). |
logs |
dict |
e.g. stdout / stderr line lists. |
The package includes a generated voidrun.api_client module. Regenerate it when the VoidRun OpenAPI specification changes (same spec as other official clients). After regen, confirm sandbox status values still match the server (e.g. running, stopped, paused, error, killed, deleted, …).
The examples/ directory contains scripts for sync/async usage, exec, FS, lifecycle, PTY, background commands, and the code interpreter.
Run all examples (loads py-sdk/.env if present, sets PYTHONPATH):
chmod +x scripts/run_all_examples.sh # once
./scripts/run_all_examples.shThe script exits with status 1 if any example fails (useful for CI).
Single example
From the py-sdk directory (with PYTHONPATH including the repo root, same as the batch script):
export VR_API_KEY="your-api-key"
export PYTHONPATH="$(pwd)"
python3 examples/sync_usage.py
python3 examples/async_usage.pyUse an API key from the same VoidRun deployment as the API host (hosted default, or set VR_API_URL for self-hosted).
cd py-sdk
pip install -e .
pip install pytest pytest-asyncio pytest-mock
pytest tests/Hatch shortcut:
hatch run test
hatch run all-examples # runs scripts/run_all_examples.shpip install build
python -m buildOr with Poetry: poetry build / poetry publish. See pyproject.toml for package metadata.
Failures raise exceptions from the OpenAPI client (for example ApiException) with HTTP status and body. Parse the error body for error and details fields returned by the VoidRun API.
from voidrun import VoidRun
from voidrun.api_client.rest import ApiException
vr = VoidRun()
try:
vr.get_sandbox("nonexistent-id")
except ApiException as e:
print(e.status, e.body)Set VR_API_KEY or pass api_key= to the constructor.
You cleared the base URL (for example empty VR_API_URL). Omit the variable to use the packaged default, or set VR_API_URL / API_URL (self-hosted) with the correct prefix (often /api).
The key must match the VoidRun API you are calling (hosted default host or your self-hosted VR_API_URL). A local .env pointing at localhost with a cloud key (or vice versa) will return 401.
Respect minimum CPU and memory enforced by your org/plan. Defaults are 1 CPU and 1024 MB; increase if the API rejects smaller values.
Increase timeout in run_command, or allow more time before closing the session.
List parent directories with sandbox.fs.list_files and confirm paths inside the sandbox.
Contributions are welcome; see the repository’s contributing guidelines if present.
- License: MIT: see the
LICENSEfile. - PyPI: voidrun
- Issues / discussions: GitHub
- Contact: [email protected] (see
pyproject.tomlauthors)
Made with care by VoidRun.