> ## Documentation Index
> Fetch the complete documentation index at: https://opensandbox-oc-s-762ac928075c46d2828bcb22.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

> Complete Python SDK reference

## Installation

```bash theme={null}
pip install opencomputer-sdk
```

```python theme={null}
from opencomputer import Sandbox, Image, Snapshots
```

***

## Sandbox

### Class Methods

#### `await Sandbox.create(...): Sandbox`

Create a new sandbox.

| Parameter      | Type                    | Default  | Description                                        |
| -------------- | ----------------------- | -------- | -------------------------------------------------- |
| `template`     | str                     | `"base"` | Template name                                      |
| `timeout`      | int                     | `0`      | Idle timeout in seconds                            |
| `api_key`      | str                     | env var  | API key                                            |
| `api_url`      | str                     | env var  | API URL                                            |
| `envs`         | dict\[str, str]         | None     | Environment variables                              |
| `metadata`     | dict\[str, str]         | None     | Arbitrary metadata                                 |
| `image`        | Image                   | None     | Declarative image definition (see [Image](#image)) |
| `snapshot`     | str                     | None     | Name of a pre-built snapshot                       |
| `on_build_log` | `Callable[[str], None]` | None     | Build log callback (when using `image`)            |

```python theme={null}
sandbox = await Sandbox.create(template="my-stack", timeout=600)
```

<Note>
  `cpuCount` and `memoryMB` are not available in the Python SDK. Use the [HTTP API](/reference/api#sandbox-lifecycle) for custom resources.
</Note>

#### `await Sandbox.connect(sandbox_id, ...): Sandbox`

| Parameter    | Type | Description               |
| ------------ | ---- | ------------------------- |
| `sandbox_id` | str  | Sandbox ID (**required**) |
| `api_key`    | str  | API key (optional)        |
| `api_url`    | str  | API URL (optional)        |

```python theme={null}
sandbox = await Sandbox.connect("sb-abc123")
```

#### `await Sandbox.create_from_checkpoint(checkpoint_id, ...): Sandbox`

| Parameter       | Type | Default | Description                  |
| --------------- | ---- | ------- | ---------------------------- |
| `checkpoint_id` | str  | —       | Checkpoint ID (**required**) |
| `timeout`       | int  | `0`     | Idle timeout                 |
| `api_key`       | str  | env var | API key                      |
| `api_url`       | str  | env var | API URL                      |

```python theme={null}
forked = await Sandbox.create_from_checkpoint("cp-abc123")
```

#### `await Sandbox.create_checkpoint_patch(checkpoint_id, script, ...): dict`

| Parameter       | Type | Required | Description       |
| --------------- | ---- | -------- | ----------------- |
| `checkpoint_id` | str  | Yes      | Target checkpoint |
| `script`        | str  | Yes      | Bash script       |
| `description`   | str  | No       | Description       |

```python theme={null}
result = await Sandbox.create_checkpoint_patch("cp-abc", script="apt install -y curl")
```

#### `await Sandbox.list_checkpoint_patches(checkpoint_id, ...): list[dict]`

```python theme={null}
patches = await Sandbox.list_checkpoint_patches("cp-abc")
```

#### `await Sandbox.delete_checkpoint_patch(checkpoint_id, patch_id, ...): None`

```python theme={null}
await Sandbox.delete_checkpoint_patch("cp-abc", "pa-xyz")
```

### Context Manager

Auto-kills the sandbox on exit:

```python theme={null}
async with await Sandbox.create() as sandbox:
    result = await sandbox.exec.run("echo hello")
    print(result.stdout)
# sandbox.kill() called automatically
```

### Instance Methods

#### `await sandbox.kill(): None`

Terminate the sandbox.

#### `await sandbox.is_running(): bool`

Check if the sandbox is running.

#### `await sandbox.set_timeout(timeout): None`

Update idle timeout.

| Parameter | Type | Description            |
| --------- | ---- | ---------------------- |
| `timeout` | int  | New timeout in seconds |

#### `await sandbox.create_checkpoint(name): dict`

Create a named checkpoint. Returns checkpoint info as a dictionary.

#### `await sandbox.list_checkpoints(): list[dict]`

List all checkpoints for the sandbox.

#### `await sandbox.restore_checkpoint(checkpoint_id): None`

Revert in-place to a checkpoint.

#### `await sandbox.delete_checkpoint(checkpoint_id): None`

Delete a checkpoint.

#### `await sandbox.create_preview_url(port, domain?, auth_config?): dict`

| Parameter     | Type | Required | Description              |
| ------------- | ---- | -------- | ------------------------ |
| `port`        | int  | Yes      | Container port (1–65535) |
| `domain`      | str  | No       | Custom domain            |
| `auth_config` | dict | No       | Auth configuration       |

#### `await sandbox.list_preview_urls(): list[dict]`

#### `await sandbox.delete_preview_url(port): None`

#### `await sandbox.close(): None`

Close HTTP clients. Called automatically by the context manager.

### Not Available in Python

These features are TypeScript-only. Use the [HTTP API](/reference/api) directly:

* `hibernate()` / `wake()`
* `cpuCount` / `memoryMB` on create

### Properties

| Property     | Type       | Description                       |
| ------------ | ---------- | --------------------------------- |
| `sandbox_id` | str        | Sandbox ID                        |
| `status`     | str        | Current status                    |
| `agent`      | Agent      | [Agent sessions](#agent)          |
| `exec`       | Exec       | [Command execution](#exec)        |
| `files`      | Filesystem | [File operations](#filesystem)    |
| `pty`        | Pty        | [Terminal sessions](#pty)         |
| `commands`   | Exec       | **Deprecated** — alias for `exec` |

***

## Agent

Accessed via `sandbox.agent`.

### `await sandbox.agent.start(...): AgentSession`

Start an agent session.

| Parameter         | Type                           | Description              |
| ----------------- | ------------------------------ | ------------------------ |
| `prompt`          | str                            | Initial prompt           |
| `model`           | str                            | Claude model             |
| `system_prompt`   | str                            | System prompt            |
| `allowed_tools`   | list\[str]                     | Restrict tools           |
| `permission_mode` | str                            | Permission mode          |
| `max_turns`       | int                            | Max turns (default: 50)  |
| `cwd`             | str                            | Working directory        |
| `mcp_servers`     | dict\[str, Any]                | MCP server configuration |
| `on_event`        | Callable\[\[AgentEvent], None] | Event callback           |
| `on_error`        | Callable\[\[str], None]        | Stderr callback          |

```python theme={null}
session = await sandbox.agent.start(
    prompt="Build a todo app",
    on_event=lambda e: print(e.type),
)
```

<Note>
  `resume`, `on_exit`, and `on_scrollback_end` are not available in the Python SDK.
</Note>

### `await sandbox.agent.attach(session_id, ...): AgentSession`

Reconnect to a running agent session. Accepts `on_event` and `on_error`.

### `await sandbox.agent.list(): list[AgentSessionInfo]`

List all agent sessions.

### AgentSession

| Member                 | Type   | Description              |
| ---------------------- | ------ | ------------------------ |
| `session_id`           | str    | Session ID               |
| `sandbox_id`           | str    | Sandbox ID               |
| `send_prompt(text)`    | method | Send follow-up prompt    |
| `interrupt()`          | method | Interrupt current turn   |
| `configure(...)`       | method | Update model, tools, cwd |
| `await kill(signal=9)` | method | Kill agent process       |
| `await close()`        | method | Close WebSocket          |

#### `await session.collect_events(): list[AgentEvent]`

Collect all events until the agent process exits. Python-unique alternative to callbacks.

```python theme={null}
session = await sandbox.agent.start(prompt="Fix the tests")
events = await session.collect_events()
for event in events:
    if event.type == "result":
        print(event.data)
```

#### `await session.wait(): int`

Wait for the agent to finish. Returns the exit code.

```python theme={null}
exit_code = await session.wait()
```

### AgentEvent

Dataclass with dict-like access:

```python theme={null}
@dataclass
class AgentEvent:
    type: str
    data: dict[str, Any]

# Usage:
event["message"]      # dict-like access
event.get("message")  # safe access with default
event.type            # attribute access
```

### AgentSessionInfo

```python theme={null}
@dataclass
class AgentSessionInfo:
    session_id: str
    sandbox_id: str
    running: bool
    started_at: str
```

***

## Exec

Accessed via `sandbox.exec`.

### `await sandbox.exec.run(command, ...): ProcessResult`

Run a command synchronously via `sh -c`.

| Parameter | Type            | Default | Description                  |
| --------- | --------------- | ------- | ---------------------------- |
| `command` | str             | —       | Shell command (**required**) |
| `timeout` | int             | `60`    | Timeout in seconds           |
| `env`     | dict\[str, str] | None    | Environment variables        |
| `cwd`     | str             | None    | Working directory            |

```python theme={null}
result = await sandbox.exec.run("npm test", cwd="/app")
```

### `await sandbox.exec.start(command, ...): ExecSession`

Start a long-running command with streaming I/O. Returns an `ExecSession` — see the [full reference](/reference/python-sdk/exec#await-sandbox-exec-start-command).

| Parameter                  | Type                      | Default | Description                              |
| -------------------------- | ------------------------- | ------- | ---------------------------------------- |
| `command`                  | str                       | —       | Command (**required**)                   |
| `args`                     | list\[str]                | None    | Arguments                                |
| `env`                      | dict\[str, str]           | None    | Environment variables                    |
| `cwd`                      | str                       | None    | Working directory                        |
| `timeout`                  | int                       | None    | Timeout in seconds                       |
| `max_run_after_disconnect` | int                       | None    | Seconds to keep running after disconnect |
| `on_stdout`                | Callable\[\[bytes], None] | None    | Stdout callback                          |
| `on_stderr`                | Callable\[\[bytes], None] | None    | Stderr callback                          |
| `on_exit`                  | Callable\[\[int], None]   | None    | Exit callback                            |

```python theme={null}
session = await sandbox.exec.start(
    "node", args=["server.js"], cwd="/app",
    on_stdout=lambda b: print(b.decode(), end=""),
)
exit_code = await session.done
```

### `await sandbox.exec.background(command, ...): ExecSession`

Alias for `start`. Same signature and return type.

### `await sandbox.exec.shell(cwd=None, env=None): Shell`

Open a stateful shell session whose `cwd`, env vars, and shell functions persist across `.run()` calls. Foreground-only. See the [full reference](/reference/python-sdk/exec#await-sandbox-exec-shell).

```python theme={null}
sh = await sandbox.exec.shell(cwd="/app")
await sh.run("export NODE_ENV=test")
r = await sh.run("npm test")
await sh.close()
```

### `await sandbox.exec.attach(session_id, ...): ExecSession`

Reconnect to a running exec session over WebSocket, with the same streaming callbacks as `start`.

### `await sandbox.exec.list(): list[ExecSessionInfo]`

List all exec sessions.

### `await sandbox.exec.kill(session_id, signal=9): None`

Kill an exec session.

### ProcessResult

```python theme={null}
@dataclass
class ProcessResult:
    exit_code: int
    stdout: str
    stderr: str
```

### ExecSessionInfo

```python theme={null}
@dataclass
class ExecSessionInfo:
    session_id: str
    sandbox_id: str
    command: str
    args: list[str]
    running: bool
    exit_code: int | None
    started_at: str
    attached_clients: int
```

***

## Filesystem

Accessed via `sandbox.files`.

### `await sandbox.files.read(path): str`

Read a file as a UTF-8 string.

### `await sandbox.files.read_bytes(path): bytes`

Read a file as raw bytes.

### `await sandbox.files.write(path, content): None`

Write content to a file. Accepts `str` or `bytes`.

### `await sandbox.files.list(path="/"): list[EntryInfo]`

List directory contents.

### `await sandbox.files.make_dir(path): None`

Create a directory.

### `await sandbox.files.remove(path): None`

Delete a file or directory.

### `await sandbox.files.exists(path): bool`

Check if a path exists. Client-side wrapper — attempts a read and returns `False` on error.

### EntryInfo

```python theme={null}
@dataclass
class EntryInfo:
    name: str
    is_dir: bool
    path: str
    size: int = 0
```

***

## Pty

Accessed via `sandbox.pty`.

### `await sandbox.pty.create(cols=80, rows=24, on_output=None): PtySession`

Create an interactive terminal session.

| Parameter   | Type                      | Default | Description      |
| ----------- | ------------------------- | ------- | ---------------- |
| `cols`      | int                       | `80`    | Terminal columns |
| `rows`      | int                       | `24`    | Terminal rows    |
| `on_output` | Callable\[\[bytes], None] | None    | Output callback  |

### PtySession

| Member          | Type   | Description                          |
| --------------- | ------ | ------------------------------------ |
| `session_id`    | str    | Session ID                           |
| `sandbox_id`    | str    | Sandbox ID                           |
| `send(data)`    | method | Send input (str or bytes)            |
| `await recv()`  | method | Receive output bytes (Python-unique) |
| `await close()` | method | Close the PTY                        |

<Note>
  `recv()` is Python-unique — a pull-based alternative to the `on_output` callback. Returns raw `bytes`.
</Note>

***

## Image

Fluent, immutable builder for declarative sandbox images. Each method returns a new `Image` instance.

### `Image.base(): Image`

Start from the default OpenSandbox environment (Ubuntu 22.04, Python, Node.js, build tools).

```python theme={null}
from opencomputer import Image

image = (
    Image.base()
    .apt_install(["curl", "jq"])
    .pip_install(["requests", "pandas"])
    .env({"PROJECT_ROOT": "/workspace"})
    .workdir("/workspace")
)
```

### Builder Methods

| Method                                    | Parameters       | Description                           |
| ----------------------------------------- | ---------------- | ------------------------------------- |
| `apt_install(packages)`                   | `list[str]`      | Install system packages via apt-get   |
| `pip_install(packages)`                   | `list[str]`      | Install Python packages via pip       |
| `run_commands(*cmds)`                     | `str (variadic)` | Run shell commands during build       |
| `env(vars)`                               | `dict[str, str]` | Set environment variables             |
| `workdir(path)`                           | `str`            | Set default working directory         |
| `add_file(remote_path, content)`          | `str, str`       | Embed a file with inline content      |
| `add_local_file(local_path, remote_path)` | `str, str`       | Read a local file into the image      |
| `add_local_dir(local_path, remote_path)`  | `str, str`       | Read a local directory into the image |

### `image.to_dict(): dict`

Returns the image manifest as a plain dict.

### `image.cache_key(): str`

Computes a deterministic SHA-256 hash of the manifest for cache lookups.

***

## Snapshots

Standalone class — not a property on Sandbox.

### `Snapshots(api_key=..., api_url=...)`

```python theme={null}
from opencomputer import Snapshots

snapshots = Snapshots()
# or with explicit config:
snapshots = Snapshots(api_key="...", api_url="...")
```

| Parameter | Type | Description                                            |
| --------- | ---- | ------------------------------------------------------ |
| `api_key` | str  | API key (falls back to `OPENCOMPUTER_API_KEY` env var) |
| `api_url` | str  | API URL (falls back to `OPENCOMPUTER_API_URL` env var) |

### `await snapshots.create(name, image, on_build_logs=None): dict`

Create a pre-built snapshot from a declarative image.

| Parameter       | Type                    | Required | Description                  |
| --------------- | ----------------------- | -------- | ---------------------------- |
| `name`          | str                     | Yes      | Unique snapshot name         |
| `image`         | Image                   | Yes      | Declarative image definition |
| `on_build_logs` | `Callable[[str], None]` | No       | Build log streaming callback |

### `await snapshots.list(): list[dict]`

List all snapshots for the current organization.

### `await snapshots.get(name): dict`

Get a snapshot by name.

### `await snapshots.delete(name): None`

Delete a snapshot. Existing sandboxes created from it are not affected.

### SnapshotInfo

Snapshot methods return dicts with these fields:

```python theme={null}
{
    "id": str,           # Unique snapshot identifier
    "name": str,         # Snapshot name
    "status": str,       # "building" | "ready" | "failed"
    "contentHash": str,  # SHA-256 hash of the image manifest
    "checkpointId": str, # Linked checkpoint ID
    "manifest": dict,    # The declarative image manifest
    "createdAt": str,    # ISO 8601 creation timestamp
    "lastUsedAt": str,   # ISO 8601 last usage timestamp
}
```

***

## Secret Stores

### `await SecretStore.create(**kwargs)`

Create a new secret store.

```python theme={null}
from opencomputer import SecretStore

store = await SecretStore.create(
    name='my-agent-secrets',
    egress_allowlist=['api.anthropic.com'],
)
```

<ParamField body="name" type="str" required>
  Store name (unique per organization).
</ParamField>

<ParamField body="egress_allowlist" type="list[str] | None" default="None">
  Allowed egress hosts (e.g. `["api.anthropic.com"]`).
</ParamField>

**Returns:** `dict` — Secret store info with `id`, `name`, `egressAllowlist`, etc.

### `await SecretStore.list(**kwargs)`

List all secret stores.

```python theme={null}
stores = await SecretStore.list()
```

**Returns:** `list[dict]`

### `await SecretStore.get(store_id, **kwargs)`

Get a secret store by ID.

```python theme={null}
store = await SecretStore.get('store-uuid')
```

<ParamField body="store_id" type="str" required>
  UUID of the secret store.
</ParamField>

**Returns:** `dict`

### `await SecretStore.update(store_id, **kwargs)`

Partial updates — only the fields you pass are changed.

```python theme={null}
updated = await SecretStore.update(
    'store-uuid',
    name='new-name',
    egress_allowlist=['api.anthropic.com', '*.openai.com'],
)
```

<ParamField body="store_id" type="str" required>
  UUID of the store to update.
</ParamField>

<ParamField body="name" type="str" default="">
  New store name (empty = no change).
</ParamField>

<ParamField body="egress_allowlist" type="list[str] | None" default="None">
  New allowed egress hosts (None = no change).
</ParamField>

**Returns:** `dict`

### `await SecretStore.delete(store_id, **kwargs)`

Deletes the store and all its secrets. Running sandboxes are not affected.

```python theme={null}
await SecretStore.delete('store-uuid')
```

**Returns:** `None`

### `await SecretStore.set_secret(store_id, name, value, **kwargs)`

Set a secret in a store. Secrets are encrypted at rest. The value is never returned by the API.

```python theme={null}
await SecretStore.set_secret('store-uuid', 'ANTHROPIC_API_KEY', 'sk-ant-...')
```

Optionally restrict which hosts can receive this secret:

```python theme={null}
await SecretStore.set_secret(
    'store-uuid',
    'ANTHROPIC_API_KEY',
    'sk-ant-...',
    allowed_hosts=['api.anthropic.com'],
)
```

<ParamField body="store_id" type="str" required>
  UUID of the secret store.
</ParamField>

<ParamField body="name" type="str" required>
  Secret name (used as the env var name in sandboxes).
</ParamField>

<ParamField body="value" type="str" required>
  Secret value (encrypted at rest, never returned by API).
</ParamField>

<ParamField body="allowed_hosts" type="list[str] | None" default="None">
  Restrict this secret to specific hosts only.
</ParamField>

**Returns:** `None`

### `await SecretStore.list_secrets(store_id, **kwargs)`

Returns secret metadata only. Values are never exposed.

```python theme={null}
entries = await SecretStore.list_secrets('store-uuid')
# [{'name': 'ANTHROPIC_API_KEY', 'allowedHosts': ['api.anthropic.com'], ...}, ...]
```

**Returns:** `list[dict]`

### `await SecretStore.delete_secret(store_id, name, **kwargs)`

```python theme={null}
await SecretStore.delete_secret('store-uuid', 'DATABASE_URL')
```

**Returns:** `None`

### Creating a Sandbox with a Secret Store

Pass the `secret_store` parameter to `Sandbox.create()` to inject the store's secrets:

```python theme={null}
from opencomputer import Sandbox

sandbox = await Sandbox.create(
    secret_store='my-agent-secrets',
    timeout=600,
)

# Secrets are available as sealed env vars
result = await sandbox.commands.run('echo $ANTHROPIC_API_KEY')
# stdout: "osb_sealed_abc123..." (sealed, not the real key)

# But outbound HTTPS requests get the real value substituted by the proxy
api_result = await sandbox.commands.run(
    'curl -s https://api.anthropic.com/v1/messages -H "x-api-key: $ANTHROPIC_API_KEY" ...'
)
# The proxy replaces the sealed token with the real key before it hits Anthropic
```
