DOCS

Python SDK

The official Python SDK — spawn sandboxes, run code and commands, and move files. Mirrors the e2b Sandbox shape.

pip install qbox-sh

The SDK mirrors the e2b Sandbox shape, so migrating is mostly a one-line import change.

A TypeScript SDK is coming soon. Until then, drive qbox from TypeScript via the REST API.

Authentication

The SDK reads QBOX_API_KEY and QBOX_BASE_URL from the environment, or you pass them explicitly via qbox.configure(...):

export QBOX_API_KEY=qbox_...
export QBOX_BASE_URL=https://qbox.your-company.internal
import qbox

qbox.configure(api_key="qbox_...", base_url="https://qbox.acme.internal")

Create an API key in the dashboard under API Keys.

Quickstart

import qbox

with qbox.Sandbox.create(template="python-3.12") as sandbox:
    # Run code in a stateful interpreter and read rich output.
    result = sandbox.run_code("import pandas as pd; pd.DataFrame({'a':[1,2,3]}).describe()")
    print(result.text)

    # Run a shell command.
    proc = sandbox.commands.run("python --version")
    print(proc.stdout)

The with block kills the sandbox on exit. To keep it running, call qbox.Sandbox.create(...) without the context manager and kill() when done.

Sandboxes auto-terminate after 5 minutes by default. Every sandbox has an auto-kill timeout (default 300 s, set server-side by QBOX_DEFAULT_SANDBOX_TIMEOUT_SECONDS). Pass timeout= on create to change it, call set_timeout(seconds) to extend a running one, or pass timeout=0 to disable the auto-kill entirely. Long-running work must raise this or it’ll be reaped mid-session.

Sandboxes

sandbox = qbox.Sandbox.create(template="python-3.12")     # spawn (auto-kills after 5 min)
sandbox = qbox.Sandbox.create(template="python-3.12", timeout=3600)  # 1-hour lifetime
sandbox = qbox.Sandbox.create(template="python-3.12", timeout=0)     # never auto-kill
sandbox = qbox.Sandbox.connect("sb_018f…")               # reconnect by id
info = sandbox.get_info()                                 # status, template, host
sandbox.set_timeout(600)                                  # extend the auto-kill window
for s in qbox.Sandbox.list():                             # active sandboxes
    print(s.id, s.status)
sandbox.kill()

Override the template’s defaults and shape the sandbox at spawn:

sandbox = qbox.Sandbox.create(
    template="python-3.12",
    cpu_count=2,
    memory_mb=2048,
    disk_size_mb=8192,             # writable-disk ceiling in MB (sparse — you
                                   # only pay for what's actually written)
    envs={"OPENAI_API_KEY": "…"},  # env vars available to processes in the sandbox
    allow_internet_access=False,   # cut outbound egress (default: on)
)

Run code

run_code executes in a stateful kernel (variables persist across calls within a context). Outputs can be text, HTML, PNG, or JSON.

out = sandbox.run_code("x = 40")
out = sandbox.run_code("print(x + 2)")   # → 42; state carried over
print(out.text)

# Stream output as it's produced:
for event in sandbox.run_code_stream("for i in range(3): print(i)"):
    print(event)

Commands

# Foreground — blocks, returns stdout/stderr/exit code.
proc = sandbox.commands.run("pytest -q")
print(proc.stdout, proc.exit_code)

# Background — returns a handle you can stream and kill.
handle = sandbox.commands.run("python server.py", background=True)
for line in handle:          # stream combined output
    print(line)
handle.kill()

sandbox.commands.list()      # running processes

Files

sandbox.files.write("/tmp/data.csv", "a,b\n1,2\n")
sandbox.files.append("/tmp/data.csv", "3,4\n")
text = sandbox.files.read("/tmp/data.csv")
entries = sandbox.files.list("/tmp")
sandbox.files.mkdir("/tmp/out")
sandbox.files.upload("./local.bin", "/tmp/local.bin")
sandbox.files.download("/tmp/result.json", "./result.json")
sandbox.files.rename("/tmp/a", "/tmp/b")
sandbox.files.remove("/tmp/b")
print(sandbox.files.exists("/tmp/data.csv"))

Interactive shell

Install the extra and open a PTY straight into the VM (the template must have SSH enabled):

pip install "qbox[shell]"
sandbox.shell()   # interactive PTY

Templates

for t in qbox.Templates.list():
    print(t.id, t.name, t.status)
t = qbox.Templates.get("tpl_…")

Build from a git repository

Build a template straight from a public https git repo. If the repo has a Dockerfile it’s built as-is; otherwise railpack infers a build. Identical inputs hit a build cache and return a ready template instantly.

build = qbox.Templates.create_from_git(
    "https://github.com/acme/myapp",
    name="my-app",
    ref="main",                       # branch, tag, or full commit SHA
    # dockerfile_path is optional — omit to auto-detect a Dockerfile, else railpack
    build_args={"NODE_ENV": "production"},
    default_memory_mb=2048,
    default_vcpus=2,
)
print(build.state, build.cache_hit, build.resolved_commit_sha)

template = build.wait_until_ready(timeout=600)   # no-op on a cache hit
sandbox = qbox.Sandbox.create(template=template.template_id)

Browser sessions

Drive headful chromium over CDP with Playwright/Puppeteer. Create from a browser-capable template (build one from the qbox/browser base image), wait until ready, then connect.

import qbox
from playwright.async_api import async_playwright

browser = qbox.Browsers.create(template="tpl_…", viewport={"width": 1280, "height": 720})
browser.wait_until_ready(timeout=60)

async with async_playwright() as pw:
    pw_browser = await pw.chromium.connect_over_cdp(browser.connect_url)
    page = await pw_browser.contexts[0].new_page()
    await page.goto("https://example.com")
    print(await page.title())

browser.terminate()

By default a session ends when the controlling CDP connection disconnects (Browserbase model). Pass keep_alive=True to survive disconnects until timeout_seconds (default 1h, max 6h) or an explicit terminate(). The live_view_url embeds an interactive view of the page in an iframe (it carries the auto-login query, so it opens directly).

The live view (WebRTC) comes up a moment after CDP does. If you embed it the instant a session goes ready, pass wait_for_live_view=True to create() so wait_until_ready() holds until the live view is actually serving — automation- only callers can leave it off so they connect as soon as CDP is up.

Errors

Every call raises typed errors so you can branch on the failure (auth, not-found, conflict, transient, timeout). Transient errors are retried automatically; the rest surface to you. See the package’s typed exceptions and the examples/ directory in the SDK for end-to-end patterns.