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

# Run a command

> Choose between buffered exec, TTY exec, and managed sessions

There are three distinct ways to run a command in a sandbox. This guide covers when to reach for each and shows a REST, CLI, and SDK example per mode. For field-level detail on request/response shapes, see [Exec](https://docs.bonya.ai/concepts/exec.md) and [Sessions](https://docs.bonya.ai/concepts/sessions.md) — this page doesn't repeat those.

| Mode | Endpoint | Use it when |
| - | - | - |
| Buffered exec | `POST /v1/sandbox/{sandbox_id}/exec` | You want one command's exit code and output as a single JSON response. |
| TTY exec | `GET /v1/sandbox/{sandbox_id}/exec` (WebSocket) | You want an interactive, transient terminal for one command. |
| Managed sessions | `POST /v1/sandbox/{sandbox_id}/sessions` | You want a named command you can list, re-attach to, or kill independently of the connection that started it. |

All three assume you already have a running `sandbox_id` — see the [Quickstart](https://docs.bonya.ai/quickstart.md) if you don't yet.

## Buffered exec

The default choice for scripts, CI, and anything where you just want a result. Synchronous, JSON in and out, no WebSocket. A non-zero `exit_code` is still a `200` — check the body, not the HTTP status, to determine command success.

```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": ["bash", "-lc", "echo hi"], "timeout_s": 30}'
```

```shell CLI theme={null}
bonya exec $SANDBOX_ID -- bash -lc 'echo hi'
```

```ts TypeScript theme={null}
const result = await sandbox.exec(["bash", "-lc", "echo hi"], { timeout: 30 });
console.log(result.stdout, result.exitCode, result.ok);
```

```python Python theme={null}
result = sandbox.exec(["bash", "-lc", "echo hi"], timeout=30)
print(result.stdout, result.exit_code, result.ok)
```

Use `check: true` (TypeScript/Go) or `check=True` (Python) to raise/throw on a non-ok result instead of inspecting `exit_code` yourself.

For output too large to buffer, a command that runs longer than you want to wait synchronously, or stdin you need to stream interactively, use the SDK's streaming exec (`execStream()`/`exec_stream()`/`ExecStream`) instead of buffered — see [Usage](https://docs.bonya.ai/sdks/typescript/usage.md#streaming-exec) for that variant. It's still one-shot: the remote process ends when the stream closes, same as buffered exec, just not blocked on a single JSON response.

## TTY exec

An HTTP upgrade to a WebSocket for a transient, interactive terminal — one-off, not tracked as a named session. This is what you want for a human typing at a live shell. See [Exec](https://docs.bonya.ai/concepts/exec.md#tty-exec) and [WebSockets](https://docs.bonya.ai/concepts/websockets.md) for the upgrade and START-frame mechanics; they aren't repeated here.

```shell cURL theme={null}
# WebSocket upgrade — shown with websocat, not curl, since curl can't
# speak the protocol after the 101 response.
websocat "wss://api.bonya.ai/v1/sandbox/$SANDBOX_ID/exec" \
  -H "Authorization: Bearer $BONYA_API_KEY"
# then send a TTYStartRequest as the START frame
```

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

```ts TypeScript theme={null}
const session = sandbox.execStream(["bash"], { tty: true, cols: 100, rows: 30 });
for await (const event of session) {
  // Stdout, Stderr, Exit events; TTY mode merges stderr into stdout
}
```

The CLI's `bonya console` is the everyday entry point for this — it's a shortcut for attaching to (or creating) a managed session named `console`, so under the hood it actually goes through the managed-session path below rather than raw TTY exec. Reach for raw TTY exec directly from an SDK when you want an interactive terminal without the persistence a named session implies.

## Managed sessions

For a command that should keep running after you disconnect — a dev server, a long build — and that you want to list, re-attach to, or kill independently of any single connection, use a managed session. Sessions are named, tracked server-side, and survive the sandbox suspending and resuming. See [Sessions](https://docs.bonya.ai/concepts/sessions.md) for the full field reference.

```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
```

```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")
```

Attaching to a session is also a WebSocket upgrade (`GET .../sessions/{session_name}/attach`) — multiple clients can attach to the same session, and a new attach preempts whoever was attached before. Listing sessions never wakes a suspended sandbox; it answers from the state captured at suspend time.

## Related

> [card] [Exec](https://docs.bonya.ai/concepts/exec.md)

> [card] [Sessions](https://docs.bonya.ai/concepts/sessions.md)

> [card] [WebSockets](https://docs.bonya.ai/concepts/websockets.md)


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