> ## 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.

# Examples

> Every core operation, side by side across cURL, the CLI, and every SDK

This page is a side-by-side comparison, not a full reference — for every parameter, flag, and field, see the [CLI Reference](https://docs.bonya.ai/cli-reference/overview.md), the [TypeScript](https://docs.bonya.ai/sdks/typescript/usage.md), [Python](https://docs.bonya.ai/sdks/python/usage.md), and [Go](https://docs.bonya.ai/sdks/go/usage.md) SDK usage pages, and [MCP tools](https://docs.bonya.ai/mcp/tools.md). Every example uses the `bonya-dev` template and `$SANDBOX_ID`/`$RUN_ID`/`$SCHEDULE_ID`/`$PREVIEW_ID`/`$SNAPSHOT_ID` set from a prior call's response, the same conventions as the [Quickstart](https://docs.bonya.ai/quickstart.md).

## Create a sandbox

Starts a new sandbox from a template. REST returns `202` immediately by default; `?wait=ready` blocks until it's up and returns `201` instead.

```shell cURL theme={null}
curl -s -X POST "https://api.bonya.ai/v1/sandbox?wait=ready" \
  -H "Authorization: Bearer $BONYA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template_id": "bonya-dev"}'
```

```shell CLI theme={null}
bonya create --template bonya-dev
```

```ts TypeScript theme={null}
import { Bonya } from "@bonya-ai/bonya";

const client = new Bonya();
const sandbox = await client.createSandbox({ template: "bonya-dev" });
console.log(sandbox.id);
```

```python Python theme={null}
from bonya import Bonya

with Bonya() as client:
    sandbox = client.create_sandbox(template="bonya-dev")
    print(sandbox.id)
```

```go Go theme={null}
client, err := bonya.NewClient()
if err != nil {
    log.Fatal(err)
}
defer client.Close()

sandbox, err := client.CreateSandbox(ctx, "bonya-dev")
if err != nil {
    log.Fatal(err)
}
fmt.Println(sandbox.ID)
```

**MCP:** the `sandbox_create` tool exposes this to any connected agent.

## Get and list sandboxes

Reconnect to a sandbox by id, or page through every sandbox in the organization, optionally filtered by state.

```shell cURL theme={null}
curl -s "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID" \
  -H "Authorization: Bearer $BONYA_API_KEY"

curl -s "https://api.bonya.ai/v1/sandbox?states=RUNNING&states=SUSPENDED" \
  -H "Authorization: Bearer $BONYA_API_KEY"
```

```shell CLI theme={null}
bonya get $SANDBOX_ID
bonya list --state running,suspended
```

```ts TypeScript theme={null}
import { Status } from "@bonya-ai/bonya";

const sandbox = await client.getSandbox(SANDBOX_ID);
for await (const summary of client.listSandboxes({ states: [Status.RUNNING, Status.SUSPENDED] })) {
  console.log(summary.id, summary.lastObservedStatus);
}
```

```python Python theme={null}
from bonya import Status

sandbox = client.get_sandbox(SANDBOX_ID)
for summary in client.list_sandboxes(states=[Status.RUNNING, Status.SUSPENDED]):
    print(summary.id, summary.last_observed_status.value)
```

```go Go theme={null}
sandbox, err := client.GetSandbox(ctx, sandboxID)

summaries, err := client.ListSandboxes(ctx, bonya.ListOptions{
    States: []bonya.Status{bonya.StatusRunning, bonya.StatusSuspended},
})
for _, summary := range summaries {
    fmt.Println(summary.ID, summary.LastObservedStatus)
}
```

**MCP:** `sandbox_get` fetches one sandbox; `sandbox_list` pages through all of them.

## Run a command (buffered exec)

One JSON request, one JSON response, no WebSocket. A non-zero exit code is still a `200` — check `exit_code` in the body.

```shell cURL theme={null}
curl -s -X POST "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/exec" \
  -H "Authorization: Bearer $BONYA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cmd": ["echo", "hello from bonya"]}'
```

```shell CLI theme={null}
bonya exec $SANDBOX_ID -- echo "hello from bonya"
```

```ts TypeScript theme={null}
const result = await sandbox.exec(["echo", "hello from bonya"]);
console.log(result.stdout, result.exitCode);
```

```python Python theme={null}
result = sandbox.exec(["echo", "hello from bonya"])
print(result.stdout, result.exit_code)
```

```go Go theme={null}
result, err := sandbox.Exec(ctx, []string{"echo", "hello from bonya"})
if err != nil {
    log.Fatal(err)
}
fmt.Println(result.Stdout(), result.ExitCode)
```

**MCP:** the `sandbox_exec` tool runs a buffered command the same way.

## Stream a command (streaming exec)

Get `Stdout`/`Stderr`/`Exit` events as they arrive, instead of waiting for the whole thing to buffer. There's no REST or CLI equivalent — buffered `exec` and the WebSocket TTY upgrade are the only wire-level modes; streaming is an SDK-level convenience over the latter.

```ts TypeScript theme={null}
import { Exit, Stderr, Stdout } from "@bonya-ai/bonya";

const session = sandbox.execStream(["bash", "-lc", "echo out; echo err >&2"]);
try {
  for await (const event of session) {
    if (event instanceof Stdout) process.stdout.write(Buffer.from(event.data).toString("utf-8"));
    else if (event instanceof Exit) console.log("exit:", event.exitCode);
  }
} finally {
  session.close();
}
```

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

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

```go Go theme={null}
session, err := sandbox.ExecStream(ctx, []string{"bash", "-lc", "echo out; echo err >&2"})
if err != nil {
    log.Fatal(err)
}
defer session.Close()

for {
    event, err := session.Next()
    if err != nil || event == nil {
        break
    }
    if stdout, ok := event.(bonya.Stdout); ok {
        fmt.Print(string(stdout.Data))
    }
}
```

There's no MCP tool for this — the MCP server's `sandbox_exec` is buffered-only, since streaming output doesn't fit a request/response tool call.

## Managed sessions (create, attach, list, kill)

A named, persistent command that outlives your connection and survives the sandbox suspending and resuming — the counterpart to exec for dev servers and other long-running processes.

```shell cURL theme={null}
# create
curl -s -X POST "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/sessions" \
  -H "Authorization: Bearer $BONYA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "dev", "cmd": ["npm", "run", "dev"]}'

# list
curl -s "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/sessions" \
  -H "Authorization: Bearer $BONYA_API_KEY"

# kill
curl -s -X POST "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/sessions/dev/kill" \
  -H "Authorization: Bearer $BONYA_API_KEY"
```

```shell CLI theme={null}
bonya session create $SANDBOX_ID dev -- npm run dev
bonya session list $SANDBOX_ID
bonya session attach $SANDBOX_ID dev
bonya session kill $SANDBOX_ID dev
```

```ts TypeScript theme={null}
const info = await sandbox.createSession("dev", ["npm", "run", "dev"]);

const session = await sandbox.attachSession("dev");
for await (const event of session) {
  // ...
}
session.detach();

for (const info of await sandbox.listSessions()) {
  console.log(info.name, info.status);
}

await sandbox.killSession("dev");
```

```python Python theme={null}
info = sandbox.create_session("dev", ["npm", "run", "dev"])

session = sandbox.attach_session("dev")
for event in session:
    ...
session.detach()

for info in sandbox.list_sessions():
    print(info.name, info.status)

sandbox.kill_session("dev")
```

```go Go theme={null}
info, err := sandbox.CreateSession(ctx, "dev", []string{"npm", "run", "dev"})

stream, err := sandbox.AttachSession(ctx, "dev")
for {
    event, err := stream.Next()
    if err != nil || event == nil {
        break
    }
}
stream.Detach()

result, err := sandbox.ListSessions(ctx)
for _, info := range result.Sessions {
    fmt.Println(info.Name, info.Status)
}

sandbox.KillSession(ctx, "dev")
```

**MCP:** `session_create`, `session_list`, and `session_kill` cover create/list/kill. Attach is a WebSocket upgrade with no request/response shape, so there's no `session_attach` tool — an agent can start and stop background work but can't stream its live output.

## Files (read/write/upload/download/list)

Read and write bodies over REST are raw bytes — not tar archives or multipart form data — so each call moves exactly one file.

```shell cURL theme={null}
# write
curl -s -X PUT "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/fs/write?path=/workspace/hello.txt" \
  -H "Authorization: Bearer $BONYA_API_KEY" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @./hello.txt

# read
curl -s "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/fs/read?path=/workspace/hello.txt" \
  -H "Authorization: Bearer $BONYA_API_KEY" \
  -o ./hello.downloaded.txt

# list
curl -s "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/fs/list?path=/workspace" \
  -H "Authorization: Bearer $BONYA_API_KEY"
```

```shell CLI theme={null}
bonya fs upload $SANDBOX_ID ./hello.txt /workspace/hello.txt
bonya fs download $SANDBOX_ID /workspace/hello.txt ./hello.downloaded.txt
bonya fs ls $SANDBOX_ID /workspace
```

```ts TypeScript theme={null}
await sandbox.writeFile("/workspace/message.txt", "hello\n");
const payload = await sandbox.readFile("/workspace/message.txt");

await sandbox.uploadFile("./hello.txt", "/workspace/hello.txt");
await sandbox.downloadFile("/workspace/hello.txt", "./hello.downloaded.txt");

const entries = await sandbox.listFiles("/workspace");
```

```python Python theme={null}
sandbox.write_file("/workspace/message.txt", "hello\n")
payload = sandbox.read_file("/workspace/message.txt")

sandbox.upload_file("./hello.txt", "/workspace/hello.txt")
sandbox.download_file("/workspace/hello.txt", "./hello.downloaded.txt")

entries = sandbox.list_files("/workspace")
```

```go Go theme={null}
sandbox.WriteFile(ctx, "/workspace/message.txt", []byte("hello\n"))
payload, err := sandbox.ReadFile(ctx, "/workspace/message.txt")

sandbox.UploadFile(ctx, "./hello.txt", "/workspace/hello.txt")
sandbox.DownloadFile(ctx, "/workspace/hello.txt", "./hello.downloaded.txt")

entries, err := sandbox.ListFiles(ctx, "/workspace")
```

**MCP:** `fs_read`, `fs_write`, and `fs_list` cover this (plus `fs_stat`, `fs_mkdir`, `fs_rename`, and `fs_delete` for the rest of the filesystem surface) — there's no upload/download tool since MCP tool calls carry inline UTF-8 text, not binary file transfers.

## Preview URLs (create/list/delete)

Publishes one guest port at an HTTPS URL a browser can open. Ports must be 1024–65535.

```shell cURL theme={null}
curl -s -X POST "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/previews" \
  -H "Authorization: Bearer $BONYA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"port": 3000, "name": "web"}'

curl -s "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/previews" \
  -H "Authorization: Bearer $BONYA_API_KEY"

curl -s -X DELETE "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/previews/$PREVIEW_ID" \
  -H "Authorization: Bearer $BONYA_API_KEY"
```

```shell CLI theme={null}
bonya preview create $SANDBOX_ID 3000 --name web
bonya preview list $SANDBOX_ID
bonya preview delete $SANDBOX_ID $PREVIEW_ID
```

```ts TypeScript theme={null}
const preview = await sandbox.createPreview(3000, { name: "web" });
console.log(preview.url);

await sandbox.listPreviews();
await sandbox.deletePreview(preview.id);
```

```python Python theme={null}
preview = sandbox.create_preview(3000, name="web")
print(preview.url)

sandbox.list_previews()
sandbox.delete_preview(preview.id)
```

```go Go theme={null}
preview, err := sandbox.CreatePreview(ctx, 3000, bonya.CreatePreviewOptions{Name: "web"})
fmt.Println(preview.URL)

previews, err := sandbox.ListPreviews(ctx)
err = sandbox.DeletePreview(ctx, preview.ID)
```

**MCP:** `preview_create`, `preview_list`, `preview_get`, `preview_delete` and the `preview_grant_*` tools cover these. A grant token is shown once, in the response that creates it.

## Snapshots

Captures a running sandbox's disk state. Restore by passing the snapshot id to sandbox create.

```shell cURL theme={null}
curl -X POST https://api.bonya.ai/v1/sandbox/$SANDBOX_ID/snapshots \
  -H "Authorization: Bearer $BONYA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "baseline"}'
```

```shell CLI theme={null}
bonya snapshot create $SANDBOX_ID
bonya snapshot delete $SANDBOX_ID $SNAPSHOT_ID
```

```ts TypeScript theme={null}
const snapshot = await sandbox.snapshot();
console.log(snapshot.id, snapshot.sourceSandboxId);

await snapshot.delete();
```

```python Python theme={null}
snapshot = sandbox.snapshot()
print(snapshot.id, snapshot.source_sandbox_id)

snapshot.delete()
```

```go Go theme={null}
snapshot, err := sandbox.Snapshot(ctx)
fmt.Println(snapshot.ID, snapshot.SourceSandboxID)

snapshot.Delete(ctx)
```

**MCP:** `snapshot_create`, `snapshot_list`, `snapshot_get`, and `snapshot_delete`; pass `snapshot` to `sandbox_create` to restore.

## Jobs (run\_job/start\_job and language equivalents)

A managed run of a command or script on a new or existing sandbox. `run_job`/`RunJob` blocks until it finishes; `start_job`/`StartJob` returns a run id immediately for you to poll later. There's no `bonya job` CLI command — jobs are reached through REST or an SDK.

```shell cURL theme={null}
# run and wait for the result (wait is a body field, not a query param)
curl -s -X POST "https://api.bonya.ai/v1/job" \
  -H "Authorization: Bearer $BONYA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"new_sandbox": {"template_id": "bonya-dev"}, "cmd": ["./run-tests.sh"], "wait": true}'

# start durably and check later (wait defaults to false)
curl -s -X POST "https://api.bonya.ai/v1/job" \
  -H "Authorization: Bearer $BONYA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"existing_sandbox_id": "'"$SANDBOX_ID"'", "cmd": ["./long-migration.sh"]}'

curl -s "https://api.bonya.ai/v1/job/$RUN_ID" \
  -H "Authorization: Bearer $BONYA_API_KEY"
```

```ts TypeScript theme={null}
const run = await client.runJob({
  newSandbox: { template: "bonya-dev" },
  cmd: ["./run-tests.sh"],
});
console.log(run.status, run.result?.exitCode);

const { runId } = await client.startJob({
  existingSandboxId: SANDBOX_ID,
  cmd: ["./long-migration.sh"],
});
const detail = await client.getJobRun(runId);
```

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

run = client.run_job(JobSpec(
    new_sandbox=JobSandboxSpec(template="bonya-dev"),
    cmd=("./run-tests.sh",),
))
print(run.status, run.result.exit_code)

run_id, already_running = client.start_job(JobSpec(
    existing_sandbox_id=SANDBOX_ID,
    cmd=("./long-migration.sh",),
))
detail = client.get_job_run(run_id)
```

```go Go theme={null}
run, err := client.RunJob(ctx, bonya.JobSpec{
    NewSandbox: &bonya.JobSandboxSpec{Template: "bonya-dev"},
    Cmd:        []string{"./run-tests.sh"},
})
fmt.Println(run.Status, run.Result.ExitCode)

runID, alreadyRunning, err := client.StartJob(ctx, bonya.JobSpec{
    ExistingSandboxID: sandboxID,
    Cmd:               []string{"./long-migration.sh"},
})
detail, err := client.GetJobRun(ctx, runID)
```

**MCP:** `job_create` covers both modes — pass `wait: true` to block for the result or leave it `false` to get a `run_id` back immediately, and use `job_get`/`job_list`/`job_cancel` from there.

## Job schedules (create/list/trigger)

Wraps a job spec with timing — cron, an interval, or a one-shot future time — so it runs without you calling the job endpoint yourself. There's no `bonya job-schedule` CLI command.

```shell cURL theme={null}
curl -s -X POST "https://api.bonya.ai/v1/job-schedule" \
  -H "Authorization: Bearer $BONYA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"interval_s": 3600, "job": {"new_sandbox": {"template_id": "bonya-dev"}, "cmd": ["./nightly-report.sh"]}}'

curl -s "https://api.bonya.ai/v1/job-schedule" \
  -H "Authorization: Bearer $BONYA_API_KEY"

curl -s -X POST "https://api.bonya.ai/v1/job-schedule/$SCHEDULE_ID/trigger" \
  -H "Authorization: Bearer $BONYA_API_KEY"
```

```ts TypeScript theme={null}
const schedule = await client.createJobSchedule(
  { intervalSeconds: 3600 },
  { newSandbox: { template: "bonya-dev" }, cmd: ["./nightly-report.sh"] },
);
console.log(schedule.scheduleId, schedule.nextRunAtUnixNanos);

for await (const s of client.listJobSchedules()) {
  console.log(s.scheduleId);
}
await client.triggerJobSchedule(schedule.scheduleId);
```

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

schedule = client.create_job_schedule(
    ScheduleSpec(interval_seconds=3600),
    JobSpec(new_sandbox=JobSandboxSpec(template="bonya-dev"), cmd=("./nightly-report.sh",)),
)
print(schedule.schedule_id, schedule.next_run_at_unix_nanos)

schedules = client.list_job_schedules()
client.trigger_job_schedule(schedule.schedule_id)
```

```go Go theme={null}
schedule, err := client.CreateJobSchedule(ctx,
    bonya.ScheduleSpec{IntervalSeconds: 3600},
    bonya.JobSpec{NewSandbox: &bonya.JobSandboxSpec{Template: "bonya-dev"}, Cmd: []string{"./nightly-report.sh"}},
)
fmt.Println(schedule.ScheduleID, schedule.NextRunAtUnixNanos)

schedules, err := client.ListJobSchedules(ctx)
err = client.TriggerJobSchedule(ctx, schedule.ScheduleID)
```

**MCP:** `job_schedule_create`, `job_schedule_list`, and `job_schedule_trigger` cover this (plus `job_schedule_get`, `_update`, `_pause`, and `_delete` for the rest of the surface).

## Templates (list templates)

Lists every `template_id`/version a create or job call will accept, with metadata like OS and preinstalled language stacks. Not paginated — the catalog is the same for every caller. There's no `bonya template` CLI command; `bonya create` offers the catalog interactively instead.

```shell cURL theme={null}
curl -s "https://api.bonya.ai/v1/templates" \
  -H "Authorization: Bearer $BONYA_API_KEY"
```

```ts TypeScript theme={null}
const templates = await client.listTemplates();
for (const template of templates) {
  console.log(template.id, template.version, template.isDefault, template.metadata.os);
}
```

```python Python theme={null}
templates = client.list_templates()
for template in templates:
    print(template.id, template.version, template.is_default, template.metadata.os)
```

```go Go theme={null}
templates, err := client.ListTemplates(ctx)
for _, template := range templates {
    fmt.Println(template.ID, template.Version, template.IsDefault, template.Metadata.OS)
}
```

**MCP:** the `template_list` tool exposes the same catalog.

## Delete/cleanup a sandbox

Deletion is a synchronous teardown — the call doesn't return until the sandbox is gone. It's idempotent: deleting an already-deleted sandbox succeeds rather than erroring.

```shell cURL theme={null}
curl -s -X DELETE "https://api.bonya.ai/v1/sandbox/$SANDBOX_ID" \
  -H "Authorization: Bearer $BONYA_API_KEY"
```

```shell CLI theme={null}
bonya delete $SANDBOX_ID
```

```ts TypeScript theme={null}
await sandbox.delete();
```

```python Python theme={null}
sandbox.delete()
```

```go Go theme={null}
sandbox.Delete(ctx)
```

**MCP:** the `sandbox_delete` tool tears down a sandbox the same way, and is likewise idempotent.

## Related

> [card] [CLI Reference](https://docs.bonya.ai/cli-reference/overview.md)

> [card] [TypeScript SDK](https://docs.bonya.ai/sdks/typescript/usage.md)

> [card] [Python SDK](https://docs.bonya.ai/sdks/python/usage.md)

> [card] [Go SDK](https://docs.bonya.ai/sdks/go/usage.md)

> [card] [MCP Server](https://docs.bonya.ai/mcp/tools.md)


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