Skip to main content
The BoxRun Python SDK provides an async client for managing boxes programmatically.
pip install boxrun-sdk

Connection

The SDK auto-connects to the BoxRun server using this priority:
  1. Unix socket at ~/.boxrun/boxrun.sock (if it exists)
  2. BOXRUN_HOST:BOXRUN_PORT environment variables
  3. Fallback to 127.0.0.1:9090
from boxrun_sdk import BoxRunClient

# Auto-detect
async with BoxRunClient() as client:
    ...

# Explicit URL
async with BoxRunClient(base_url="http://localhost:9090") as client:
    ...

BoxRunClient

create

await client.create(image, *, name=None, cpu=2, memory_mb=1024, disk_size_gb=8,
                    network=False, workdir="/root", env=None, volumes=None) -> BoxHandle
Create a new box and return a handle to it.
ParameterTypeDefaultDescription
imagestr(required)Container image (or alias like "ubuntu")
namestr | NoneNoneHuman-readable name
cpuint2CPU cores
memory_mbint1024Memory in MB
disk_size_gbint8Disk size in GB
networkboolFalseEnable networking
workdirstr"/root"Working directory
envdict[str, str] | NoneNoneEnvironment variables
volumeslist[dict] | NoneNoneVolume mounts
Volume dict format: {"host_path": "/src", "guest_path": "/root/src", "readonly": False}
box = await client.create("ubuntu:24.04", name="dev", memory_mb=1024)

run

await client.run(image, cmd, *, env=None, timeout_ms=None, disk_size_gb=8, volumes=None) -> RunResult
Ephemeral one-shot: create a box, run a command, return the result, destroy the box.
result = await client.run("ubuntu:24.04", ["echo", "hello"])
print(result.stdout)  # "hello\n"

get_box

await client.get_box(id_or_name) -> BoxInfo
Get info about a box by ID or name.

list_boxes

await client.list_boxes(status=None) -> list[BoxInfo]
List all boxes. Optionally filter by status ("running", "stopped").

gc

await client.gc(older_than=3600) -> int
Garbage collect stopped boxes older than older_than seconds. Returns the number of boxes removed.

BoxHandle

A handle to a specific box, obtained from client.create().

Properties

PropertyTypeDescription
idstrBox ID
namestr | NoneBox name
infoBoxInfoFull box info (call refresh() to update)

exec

await box.exec(cmd, *, env=None, timeout_ms=None) -> ExecInfo
Execute a command and wait for completion.
result = await box.exec(["echo", "hello"])
print(result.exit_code)  # 0

exec_stream

box.exec_stream(cmd, *, env=None, timeout_ms=None) -> AsyncIterator[ExecEvent]
Execute a command and stream output events in real time.
async for event in box.exec_stream(["apt-get", "update"]):
    if event.type == "log":
        print(event.data, end="")

upload

await box.upload(local_path, dest_path) -> None
Upload a file from the host to the box.
await box.upload("./script.py", "/root/script.py")

download

await box.download(remote_path, local_path) -> None
Download a file from the box to the host.
await box.download("/root/output.json", "./output.json")

stop / start / remove

await box.stop() -> BoxInfo          # Stop (preserves disk)
await box.start() -> BoxInfo         # Restart a stopped box
await box.remove(force=False) -> None  # Remove permanently

refresh

await box.refresh() -> BoxInfo
Re-fetch box info from the server and update self.info.

Data types

BoxInfo

@dataclass
class BoxInfo:
    id: str
    name: str | None
    status: str              # "creating", "running", "stopped", "error"
    image: str
    cpu: int
    memory_mb: int
    disk_size_gb: int
    network: bool
    workdir: str
    env: dict[str, str] | None
    volumes: list[dict] | None
    boxlite_id: str | None
    error_code: str | None
    error_message: str | None
    created_at: str
    started_at: str | None
    stopped_at: str | None

ExecInfo

@dataclass
class ExecInfo:
    id: str
    box_id: str
    status: str              # "running", "succeeded", "failed", "canceled"
    cmd: list[str]
    env: dict[str, str] | None
    workdir: str | None
    timeout_ms: int | None
    exit_code: int | None
    error_message: str | None
    created_at: str
    finished_at: str | None

ExecEvent

@dataclass
class ExecEvent:
    type: str                # "log" | "exit"
    data: str
    stream: str | None       # "stdout" | "stderr" (for log events)
    seq: int

RunResult

@dataclass
class RunResult:
    exit_code: int | None
    stdout: str
    stderr: str
    error_message: str | None

Error handling

All SDK errors raise BoxRunError(code, message).
from boxrun_sdk import BoxRunError

try:
    box = await client.create("ubuntu:24.04")
except BoxRunError as e:
    print(f"Error [{e.code}]: {e.message}")
Error codeDescription
BOX_NOT_FOUNDBox does not exist
BOX_NOT_RUNNINGBox is stopped
BOX_ALREADY_RUNNINGBox is already running
EXEC_NOT_FOUNDExecution does not exist
EXEC_ALREADY_FINISHEDExecution already completed
IMAGE_PULL_FAILEDFailed to pull container image
NAME_ALREADY_EXISTSA box with that name already exists
RESOURCE_EXCEEDEDCPU or memory limit exceeded
TIMEOUTOperation timed out
RUNTIME_ERRORInternal runtime error
CANCELEDOperation was canceled

AI agent patterns

A typical agent workflow with BoxRun:
  1. Create a box with the right image and resources
  2. Install dependencies via exec
  3. Upload code or data
  4. Run the task, streaming output for real-time feedback
  5. Download results
  6. Remove the box when done
async def agent_task():
    async with BoxRunClient() as client:
        box = await client.create("ubuntu:24.04", name="agent-workspace")

        # Install dependencies
        await box.exec(["apt-get", "update", "-y"])
        await box.exec(["apt-get", "install", "-y", "python3", "python3-pip"])

        # Upload code
        await box.upload("./script.py", "/root/script.py")

        # Run with real-time streaming
        async for event in box.exec_stream(["python3", "/root/script.py"]):
            if event.type == "log":
                print(event.data, end="")

        # Download results
        await box.download("/root/output.json", "./output.json")

        await box.remove(force=True)
For simple one-shot tasks, use client.run():
result = await client.run("python", ["python3", "-c", "print(42)"])
print(result.stdout)  # "42\n"