> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bonya.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Usage

> Sandboxes, exec, sessions, files, previews, snapshots, jobs, templates, and secrets from Python

## What you can do

| I want to… | Call |
| - | - |
| Start a sandbox | `client.create_sandbox(template=, image=, snapshot=, gpu=, network=, env=, secret_sets=)` |
| Reconnect to one | `client.get_sandbox(id)` / `.get_sandbox_by_name(name)` |
| Find my sandboxes | `client.list_sandboxes(states=, name=)` |
| Run a command | `sandbox.exec(cmd)` |
| Stream output live | `sandbox.exec_stream(cmd)` |
| Keep a process running after I disconnect | `sandbox.create_session(name, cmd)` / `.attach_session(name)` |
| Move files in and out | `sandbox.read_file` / `.write_file` / `.upload_file` / `.download_file` / `.list_files` / `.stat_file` / `.mkdir_file` / `.remove_file` / `.move_file` |
| Expose a port to the web | `sandbox.create_preview(port)` |
| Capture disk state | `sandbox.snapshot()` / `.list_snapshots()` |
| Clone a running sandbox | `sandbox.fork(count)` |
| Pause or keep awake | `sandbox.pause("warm" \| "cold")` / `.keepalive(seconds)` / `.resume()` |
| Restrict egress | `sandbox.update_network(NetworkPolicy(...))` |
| Run a managed or scheduled job | `client.run_job(spec)` / `client.create_job_schedule(schedule, job)` |
| See GPU capacity | `client.list_gpu_options()` |
| Save a launch configuration | `client.create_template(name, config=TemplateConfig(...))` |
| Pull private images | `client.create_registry_connection(...)` |
| Keep credentials out of code | `client.create_secret(name, value)` / `client.create_secret_set(name, values)` |
| Switch organization | `client.list_organizations()` / `client.organization_id = id` |

Everything is a **flat method** on `Bonya` or `Sandbox`.

> [!NOTE]
>
> `Bonya` also has convenience methods that take a sandbox id, such as
> `client.create_session(sandbox_id, name, cmd)`. Each fetches the sandbox first,
> so prefer calling on `sandbox` when you already hold one.

## Create sandboxes

```python theme={null}
from bonya import Bonya, Wait

with Bonya() as client:
    sandbox = client.create_sandbox(
        template="bonya-dev",
        wait=Wait.READY,
        idempotency_key="create-job-123",
    )
```

| Argument | Description |
| - | - |
| `template` | Template id. Omit for the deployment default. |
| `template_revision` | Pin a template revision. |
| `image` | Start from a public OCI image instead of a template. |
| `snapshot` | Start from a snapshot id. |
| `gpu` | `GpuRequest(type, count)`. Requires `image` (or an image template). |
| `network` | `NetworkPolicy`: `open`, `allowlist` or `airgap`. |
| `env` | Environment variables. |
| `secret_sets` | Secret set **names** to inject. |
| `name` | Display name. Names are not unique. |
| `wait` | `Wait.READY` (default) or `Wait.NONE`. |
| `idempotency_key` | Generated and reused across retries if omitted. |

`Sandbox` is a context manager that deletes the sandbox on exit. If create
exhausts its deadline the SDK raises `SandboxCreationTimeoutError`, which
carries the `idempotency_key` so you can retry the same creation.

## Get, list, and find by name

```python theme={null}
sandbox = client.get_sandbox("sb-abc123")
by_name = client.get_sandbox_by_name("scratch")

for item in client.list_sandboxes(states=["running", "warm"]):
    print(item.id, item.state)

sandbox.refresh()  # update the handle's fields
```

## Run commands

```python theme={null}
result = sandbox.exec(
    ["bash", "-lc", "uname -a"],
    cwd="/workspace",
    timeout=30,
    check=True,  # raise ExecFailedError on a non-zero exit
)
print(result.exit_code, result.stdout, result.stderr)
```

### Streaming exec

```python theme={null}
from bonya import Exit, Stderr, Stdout

with sandbox.exec_stream(["bash", "-lc", "echo out; echo err >&2"]) as session:
    for event in session:
        if isinstance(event, Stdout):
            print("out:", event.data.decode(), end="")
        elif isinstance(event, Stderr):
            print("err:", event.data.decode(), end="")
        elif isinstance(event, Exit):
            print("exit:", event.exit_code)
```

The process dies when the stream closes. For anything that must outlive your
connection, use a session.

## Persistent sessions

```python theme={null}
sandbox.create_session("server", ["bash"])

with sandbox.attach_session("server") as stream:
    stream.write("npm run dev\n")
    for event in stream:
        ...  # Stdout, Exit, SessionEnded, SessionOutputDropped
# leaving the block detaches; the process keeps running

print(sandbox.list_sessions())
sandbox.kill_session("server")
```

Session names match `^[a-z][a-z0-9-]{0,31}$`.

## Files

```python theme={null}
sandbox.write_file("/workspace/hello.txt", "hello\n")
data = sandbox.read_file("/workspace/hello.txt")  # bytes

sandbox.mkdir_file("/workspace/out")
sandbox.move_file("/workspace/hello.txt", "/workspace/out/hello.txt")

for entry in sandbox.list_files("/workspace/out"):
    print(entry.name, entry.size)

sandbox.upload_file("./local.json", "/workspace/out/local.json")
sandbox.download_file("/workspace/out/local.json", "./copy.json")
sandbox.remove_file("/workspace/out", recursive=True)
```

## Previews

```python theme={null}
from bonya import PreviewAuth

sandbox.create_session("web", ["python3", "-m", "http.server", "3000"])

# Ports must be 1024-65535. PRIVATE is the default.
preview = sandbox.create_preview(3000)
print(preview.url)

# Single-use entry point for a private preview. Open it once.
print(sandbox.preview_browser_url(preview))

public = sandbox.create_preview(8080, auth=PreviewAuth.PUBLIC, ttl_seconds=3600)

grant = sandbox.create_preview_grant(preview.id, ttl_seconds=600)
sandbox.renew_preview_grant(preview.id, grant.id)
sandbox.revoke_preview_grant(preview.id, grant.id)

sandbox.delete_preview(preview.id)
```

Grant tokens are shown once, in the response that creates them.

## Lifecycle, snapshots, and forks

```python theme={null}
from bonya import NetworkPolicy, NetworkRule

sandbox.pause("warm")             # or "cold"
sandbox.keepalive(30 * 60)        # keep awake; also wakes a paused sandbox
sandbox.resume()                  # wakes via keepalive

sandbox.update_network(
    NetworkPolicy(mode="allowlist", allow=[NetworkRule(domain="pypi.org")])
)

snapshot = sandbox.snapshot(name="baseline")
restored = client.create_sandbox(snapshot=snapshot.id)
forks = sandbox.fork(2).sandboxes
snapshot.delete()
```

## Jobs

```python theme={null}
from bonya import JobSandboxSpec, JobSpec

run = client.run_job(
    JobSpec(new_sandbox=JobSandboxSpec(), cmd=["./run-tests.sh"], command_timeout_seconds=600)
)
print(run.status, run.result.exit_code)
```

`run_job` waits for the result; `start_job` returns immediately. Use
`get_job_run(id)`, `list_job_runs()` and `cancel_job_run(id)` to track runs.

### Schedules

```python theme={null}
from bonya import ScheduleSpec

schedule = client.create_job_schedule(
    ScheduleSpec(cron_expressions=["0 3 * * *"], time_zone="UTC"),
    JobSpec(new_sandbox=JobSandboxSpec(), cmd=["./nightly.sh"]),
)

client.set_job_schedule_paused(schedule.schedule_id, True)
client.trigger_job_schedule(schedule.schedule_id)
client.delete_job_schedule(schedule.schedule_id)
```

A schedule uses exactly one of `cron_expressions`, `interval_seconds` or
`run_at_unix_nanos`.

## Templates

```python theme={null}
from bonya import TemplateConfig

template = client.create_template(
    "node-dev",
    config=TemplateConfig(image="node:22", cpu=2, memory_mib=4096, env={"NODE_ENV": "development"}),
)

client.update_template(template.id, config=TemplateConfig(image="node:22", cpu=4))  # new revision
print(client.list_templates())
client.delete_template(template.id)
```

## Registry connections, secrets, and secret sets

```python theme={null}
conn = client.create_registry_connection("ghcr", "ghcr.io", "me", "<token>")
client.validate_registry_connection(conn.id, "registry.example.com/team/app:latest")

client.create_secret("DB_PASSWORD", "s3cret")
client.create_secret_set("app-env", {"API_URL": "https://example.com"})

sandbox = client.create_sandbox(secret_sets=["app-env"])
```

Secret and secret-set values are **write-only**; the API never returns them.
Reference secret sets by **name**.

## Organizations

```python theme={null}
for org in client.list_organizations():
    print(org.id, org.name)

client.organization_id = "org_123"  # sent as X-Bonya-Organization-ID
```

The precedence is the constructor argument, then `BONYA_ORGANIZATION_ID`, then
your personal organization. Creating organizations, members, invites and API
keys is done in the dashboard, not through the SDK.

## Errors

Every error extends `BonyaError` (with `sandbox_id`, `idempotency_key`, `code`).
Catch the class you care about: `AuthenticationError`, `SandboxNotFoundError`,
`SandboxCreationTimeoutError`, `ExecFailedError`, `TimeoutError`,
`NoCapacityError`, `RateLimitedError`, `OutcomeUnknownError`,
`RemoteFileNotFoundError`, and so on. API keys and capability tokens are
redacted from error messages.

```python theme={null}
from bonya import SandboxNotFoundError

try:
    client.get_sandbox("sb-missing")
except SandboxNotFoundError:
    print("gone")
```

Server errors are RFC 9457 `problem+json`; see [Errors](https://docs.bonya.ai/concepts/errors.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.