Python SDK

Python SDK reference

The orkestr Python package is the official client for the sandbox API. Sync only in v1 (an async AsyncSandbox is planned). Requires Python 3.10 or newer.

Private beta
Sandboxes are in private beta and not yet publishable to PyPI. Request access and we'll send install instructions.

Install

terminal
pip install orkestr

Authentication

Every request is authenticated with a Bearer API token. The SDK looks for it in three places, in order:

  • The api_key= kwarg on Sandbox.create() / Sandbox.get() / etc.
  • The client= kwarg (a pre-built OrkestrClient)
  • The ORKESTR_API_KEY environment variable
auth.py
import os
from orkestr import Sandbox

# 1) Implicit: SDK reads ORKESTR_API_KEY from env
sbx = Sandbox.create(template="python-3.12")

# 2) Explicit: per-call api_key
sbx = Sandbox.create(template="python-3.12", api_key="ork_...")

# 3) Reusable client (good for long-running agents)
from orkestr import OrkestrClient
client = OrkestrClient(api_key="ork_...", base_url="https://api.orkestr.eu")
sbx = Sandbox.create(template="python-3.12", client=client)
Scoped tokens
Mint tokens scoped only to sandboxes:read and sandboxes:write for agent runtimes. A scoped token cannot reach the rest of the orkestr platform if it leaks.

Sandbox class

Sandbox is the main public class. Static constructors create / fetch / list / resume sandboxes; instance methods drive exec, files, and lifecycle.

Sandbox.create

python
from orkestr import Sandbox

sbx = Sandbox.create(
    template="python-3.12",
    cpu=1.0,
    memory_mb=2048,
    network="off",
    timeout_seconds=600,
    env={"OPENAI_API_KEY": "sk-..."},
    metadata={"agent_run": "r_42"},
    region="fsn1",
)
print(sbx.id, sbx.status)
ParameterTypeDescription
templaterequiredTemplateOne of python-3.12, python-3.12-bare, node-22, ubuntu-24.04.
cpufloatvCPU allocation. Default 1.0. Plan-capped (free 0.5, pro up to 2, team up to 4).
memory_mbintRAM in MB. Default 2048. Plan-capped (free 512, pro up to 4096, team up to 8192).
networkNetworkModeoff (default, no egress), restricted (curated allowlist), open (full egress, requires verified payment).
timeout_secondsintSandbox auto-terminates after this many seconds. Default 600. Max 3600 on Pro and Team.
envdict[str, str]Environment variables exposed to processes in the sandbox. In-memory only, never persisted past terminate.
metadatadict[str, str]Caller-defined tags echoed back on every response. Max 16 keys, 256 chars per value.
regionstr | NoneRegion preference. fsn1 (Falkenstein, DE) or hel1 (Helsinki, FI). Omit to let the control plane pick.

Sandbox.get and Sandbox.list

python
# Reattach from another process
sbx = Sandbox.get("sbx_01HXYZ...")

# List your sandboxes
for sbx in Sandbox.list(status="running"):
    print(sbx.id, sbx.template, sbx.created_at)

Sandbox.get reattaches to an existing sandbox by id - useful across worker processes. Sandbox.list returns the caller's sandboxes; filter by status server-side, bounded by limit (default 50, max 200).

sbx.exec

Run a shell command and return the buffered result. Raises ExecTimeout if the command exceeds timeout_seconds; the sandbox itself stays alive so you can run another command.

python
result = sbx.exec("python /workspace/main.py", timeout_seconds=60)
result.stdout       # str
result.stderr       # str
result.exit_code    # int
result.duration_ms  # int
ParameterTypeDescription
commandrequiredstrShell command to run inside the sandbox.
cwdstrWorking directory. Default /workspace.
envdict[str, str]Per-call env overrides on top of the sandbox env.
timeout_secondsintPer-command timeout, default 60. Capped at 3600. Process gets SIGTERM, then SIGKILL after 2s.

sbx.exec_stream

Stream output as it arrives. Returns an iterator of ExecChunk objects; iteration ends with a final chunk where is_final is true. Iterate to completion - breaking early leaves the in-VM process running until its own timeout fires.

python
for chunk in sbx.exec_stream("python long_task.py"):
    if chunk.stream == "stdout":
        print(chunk.data, end="", flush=True)
    elif chunk.stream == "stderr":
        print(chunk.data, end="", flush=True, file=sys.stderr)
    if chunk.is_final:
        print(f"exit={chunk.exit_code} duration={chunk.duration_ms}ms")

Files namespace

sbx.files is the file operations namespace. Writes go to writable roots (/workspace and /tmp); reads and directory listings work anywhere readable inside the sandbox.

python
# Text I/O
sbx.files.write("/workspace/script.py", "print('hello')")
content = sbx.files.read("/workspace/output.txt")

# Binary I/O
sbx.files.write_bytes("/workspace/blob.bin", b"\x00\x01\x02")
raw = sbx.files.read_bytes("/workspace/blob.bin")

# Directory listing
for entry in sbx.files.list("/workspace"):
    print(entry.name, entry.is_dir, entry.size, entry.mode)

# Remove
sbx.files.delete("/workspace/output.txt")

sbx.pause and Sandbox.resume

pause() snapshots the sandbox and stops the compute meter. Returns the sandbox id (same as sbx.id) - persist it wherever your agent keeps state and pass to Sandbox.resume() when you want to come back.

python
# Pause: returns the sandbox id, stops compute meter
sandbox_id = sbx.pause()

# ... later ...
sbx = Sandbox.resume(sandbox_id)

sbx.terminate (and context manager)

terminate() stops the sandbox and frees the resources. Idempotent. The recommended pattern is the context manager, which always terminates on exit:

python
with Sandbox.create(template="python-3.12") as sbx:
    result = sbx.exec("python -c 'print(2+2)'")
    print(result.stdout)
# sbx.terminate() is called automatically on exit (including on exceptions)

Result types

All result types are frozen dataclasses. No magic accessors - attributes are exactly what's documented here and what the API sends. ExecResult has stdout, stderr, exit_code, duration_ms. ExecChunk has stream, data, exit_code (only on final), duration_ms (only on final), plus is_final for convenience. FileEntry has name, is_dir, size, mode, modified_at.

Errors

Every exception the SDK raises inherits from OrkestrError. Catch the base for anything the SDK throws; catch a specific subclass to handle one failure mode.

python
from orkestr import (
    OrkestrError,
    AuthError,
    SandboxNotFound,
    ExecTimeout,
    SnapshotCapReached,
)

try:
    sbx = Sandbox.create(template="python-3.12")
    result = sbx.exec("sleep 9999", timeout_seconds=5)
except ExecTimeout:
    print("Command exceeded timeout - the sandbox is still running.")
except AuthError as e:
    print(f"Auth failed ({e.status_code}): {e.detail}")
except OrkestrError as e:
    print(f"Something else broke: {e}, request_id={e.request_id}")
ParameterTypeDescription
AuthErrorOrkestrErrorAPI key missing, invalid, expired, or scope insufficient. 401 / 403.
RateLimitErrorOrkestrErrorPlan rate limit hit. retry_after attribute carries the recommended wait.
PlanLimitErrorOrkestrErrorPlan tier limit hit (concurrent sandboxes, monthly CPU-seconds).
SnapshotCapReachedPlanLimitErrorpause() blocked because the snapshot retention cap is full.
SandboxNotFoundOrkestrErrorSandbox id doesn't exist or doesn't belong to the caller. 404.
SandboxNotReadyOrkestrErrorOperation called on a sandbox in the wrong state (e.g. exec on paused). 409.
ExecTimeoutOrkestrErrorexec exceeded timeout_seconds. Sandbox stays alive.
ExecKilledOrkestrErrorExec process exited via a signal. signal attribute carries the signal number.
NetworkPolicyErrorOrkestrErrorIn-sandbox code tried to reach a host blocked by the network policy.

Advanced: OrkestrClient

The default client is built lazily from the env var on first use. For long-running agents you may want to construct one explicitly and pass it to every Sandbox.* call - this lets you change the base URL (e.g. for testing against a staging deployment), configure timeouts, or swap the underlying HTTP client. See REST API reference for raw wire format if you want to bypass the SDK entirely.