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

# Boat Public API v1

> Create, manage, prompt, observe, and expose sandboxes from your own products.

Use the Boat Public API from services, CI jobs, hosted workers, and product automation code. For typed clients, start with the [Boat SDKs](/sdks/overview), [Python SDK](/sdks/python), or [TypeScript/JavaScript SDK](/sdks/typescript).

## Recommended flow

1. Create a Boat API key with `boat api-key create` or in the dashboard.
2. Create a sandbox with `POST /sandboxes`, or resume/fork an existing sandbox when the user is continuing prior work.
3. Poll `GET /sandboxes/{sandboxId}` until the sandbox is `ready` or `idle`.
4. Queue work with `POST /sandboxes/{sandboxId}/prompt`.
5. Read `GET /sandboxes/{sandboxId}/events` while work is running.
6. Use `POST /sandboxes/{sandboxId}/desktop` when the user or your support team needs live computer-use visibility.
7. Stop/archive, resume, fork, or delete the sandbox according to your retention policy.

Everything the Boat CLI does programmatically is available here: first-class endpoints cover lifecycle, prompts, events, files, desktop, and snapshots. Anything else, including in-sandbox tools like `host`, package installs, or your own scripts, is driven through [`POST /sandboxes/{sandboxId}/commands`](/api/reference/agent/execute-sandbox-command). For high-frequency or streaming control, run your own daemon in the sandbox; see [the daemon pattern](/platform-guide#bring-your-own-harness-the-daemon-pattern).

## Base URL

```text theme={null}
https://boat.dev/api/v1
```

## Authentication

Pass a Boat API key as a bearer token on API requests:

```bash theme={null}
curl -sS "https://boat.dev/api/v1/sandboxes" \
  -H "Authorization: Bearer $BOAT_API_KEY"
```

Create, rotate, and revoke service keys with the `boat api-key` CLI command or in the Boat dashboard. For key lifecycle guidance, see [API Keys](/api-keys).

<Warning>
  Treat Boat API keys and returned desktop URLs as secrets. Desktop and VNC URLs can contain access tokens and should not be logged or persisted unredacted.
</Warning>

Updating `PATCH /account/data-retention` is the exception: it requires an interactive sandbox session and refuses API keys. Per-Sandbox keys are restricted to their sandbox; use an account service key for account-wide reads.

## Response envelope

Every v1 JSON response has an explicit success discriminator:

```json theme={null}
{
  "ok": true,
  "type": "sandbox.list",
  "sandboxes": []
}
```

| Field  | Description                                                                                                                |
| ------ | -------------------------------------------------------------------------------------------------------------------------- |
| `ok`   | `true` for success, `false` for failure.                                                                                   |
| `type` | Stable response or event discriminator, such as `sandbox.list`, `sandbox.created`, `sandbox.stopping`, or `prompt.queued`. |

## Error model

HTTP status remains authoritative. Error bodies also use a structured JSON envelope:

```json theme={null}
{
  "ok": false,
  "type": "sandbox.error",
  "status": 409,
  "code": "provider_not_configured",
  "message": "Prompting is locked until Codex is configured on the Agents page.",
  "error": {
    "code": "provider_not_configured",
    "message": "Prompting is locked until Codex is configured on the Agents page.",
    "status": 409,
    "details": {
      "provider": "codex",
      "setupUrl": "https://boat.dev/dashboard?tab=agents"
    }
  },
  "requestId": "req_..."
}
```

| Status | Typical codes                                                                                                                           | What to do                                                                                                       |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `400`  | `invalid_json`, `prompt_required`, `provider_required`, `invalid_name`, `no_changes`, `machine_not_running`, `trial_auto_stop_required` | Fix the request body, wait for the sandbox machine to start, or retry once the sandbox reaches a runnable state. |
| `401`  | `unauthorized`                                                                                                                          | Provide a valid bearer token.                                                                                    |
| `402`  | `billing_required`, `team_member_cap_reached`                                                                                           | Send the user to the returned billing URL, or ask the organization owner to raise the member's usage cap.        |
| `403`  | `trial_machine_class_not_allowed`, `org_suspended`                                                                                      | The requested machine size needs a payment method, or the organization wallet is suspended.                      |
| `404`  | `not_found`                                                                                                                             | Refresh local state; the sandbox or key no longer exists.                                                        |
| `409`  | `account_not_ready`, `provider_not_configured`, `sandbox_not_promptable`, `resume_failed`, `fork_failed`                                | Resolve the prerequisite or resource-state conflict.                                                             |
| `429`  | `rate_limited`, `daily_limit_reached`, `limit_reached`, `member_limit_reached`                                                          | Back off and show the limit message.                                                                             |
| `5xx`  | `invalid_json_response`, `stream_failed`, `http_500`                                                                                    | Retry idempotent calls with jitter and include `requestId` in support logs.                                      |

## Idempotent sandbox creation

`POST /sandboxes` and `POST /sandboxes/{sandboxId}/fork` each provision a new sandbox that bills on success, so a lost response (timeout or `5xx` after the sandbox was already accepted) leaves you unsure whether to retry. Send an `Idempotency-Key` header with your own account-unique value (a UUID) to make create and fork safe to retry: the first request creates the sandbox and binds it to the key; every retry with the **same account, key, and request body** returns that same sandbox instead of a second, billable one.

```bash theme={null}
curl -sS -X POST "$BOAT_API_BASE/sandboxes" \
  -H "Authorization: Bearer $BOAT_API_KEY" \
  -H "Idempotency-Key: 6f9619ff-8b86-d011-b42d-00cf4fc964ff" \
  -H "Content-Type: application/json" \
  -d '{"from":"web-stack","noEnv":true,"ttlSeconds":1800}'
```

| Situation                                            | Result                                                                  |
| ---------------------------------------------------- | ----------------------------------------------------------------------- |
| Retry after a lost `202` or a `5xx`                  | Same key returns the original sandbox (no second sandbox).              |
| Retry while the first sandbox is still being created | `409` `idempotency_in_progress`. Retry shortly with the same key.       |
| Same key, different request body                     | `409` `idempotency_key_reused`.                                         |
| Create that failed before the sandbox existed        | Key auto-releases within \~2 minutes so a retry can create the sandbox. |

Keys are retained for 24 hours, and are scoped to your account (two accounts can use the same key value with no collision). Omit the header to keep the default (non-idempotent) behavior. The header is optional on every SDK's create and fork call (`Idempotency-Key` / `idempotencyKey`); see [Create sandbox](/api/reference/sandboxes/create-sandbox) and [Fork sandbox](/api/reference/sandboxes/fork-sandbox).

## Sandbox lifecycle

A Sandbox moves through these states:

| State                                    | Meaning                                                | Client behavior                                                             |
| ---------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------- |
| `provisioning`, `provisioned`, `cloning` | The sandbox is being created or prepared.              | Poll `GET /sandboxes/{sandboxId}`.                                          |
| `ready`, `idle`                          | The sandbox can accept prompts and interactive access. | Prompt, open desktop, SSH, or inspect events.                               |
| `running`                                | The Sandbox is actively working.                       | Read events; interrupt only when the caller intends to stop current work.   |
| `archiving`                              | Stop/snapshot is in progress.                          | Poll until `archived` or another terminal state.                            |
| `archived`                               | The machine is stopped; a snapshot may be available.   | Resume or fork.                                                             |
| `error`                                  | Provisioning or runtime failed.                        | Show the error and let the user stop, delete, resume, or fork when allowed. |

<Note>
  `idle` and `running` only reflect work queued through `POST /sandboxes/{sandboxId}/prompt`. Processes you run yourself, over SSH or the command endpoint, do not change the state: a sandbox can show `idle` while your own agent works inside it.
</Note>

## Providers, models, and reasoning

`POST /sandboxes/{sandboxId}/prompt` runs work through an agent harness (provider):

| Provider      | Aliases     | Notes                                                                   |
| ------------- | ----------- | ----------------------------------------------------------------------- |
| `codex`       |             | OpenAI Codex / ChatGPT models                                           |
| `claude-code` | `claude`    | Claude Code models                                                      |
| `pi`          |             | Multi-model: Anthropic, OpenAI, and OpenRouter/llmgateway-routed models |
| `opencode`    |             | Multi-model                                                             |
| `prime-agent` | `prime`     | Multi-model                                                             |
| `kimi`        | `kimi-code` | Kimi Code CLI: Kimi models (subscription or Moonshot API key)           |

Omit `provider` to use the harness the user selected on the Agents dashboard.

**Models and reasoning effort are harness- and model-specific and change over time**, so fetch them from the live catalog rather than hardcoding lists: `GET /provider-models` returns, per provider, every model and which `reasoningEffort` levels it accepts (`none`, `low`, `medium`, `high`, `xhigh`, `max`; some models accept none). The `boat prompt --help` CLI command prints the same catalog. `pi`, `opencode`, and `prime-agent` also expose OpenRouter- and llmgateway-routed models (ids like `openrouter:anthropic/claude-sonnet-4.5`). If you omit `model`, Boat uses the user's saved dashboard default for that harness.

Use the dashboard's provider setup page to configure credentials before prompting. If credentials are missing, the API returns `provider_not_configured` with a setup URL.

<Note>
  A Sandbox runs **many conversations in parallel**. `POST /prompt` takes `new` or `conversationId` and returns the `conversationId`; `GET /events` and `POST /interrupt` take a `conversation` filter. See [Integrated agents](/integrated-agents).
</Note>

## Request examples

These are raw HTTP. Every one of them has a CLI, TypeScript and Python equivalent on the feature page it belongs to: [Snapshots](/snapshots), [Environments](/environments), [Webhooks](/webhooks), [Data retention](/data-retention), [Desktop](/desktop-streaming), [Hosting](/hosting). The [CLI reference](/cli-reference) maps commands to endpoints.

Set shared variables:

```bash theme={null}
export BOAT_API_BASE="https://boat.dev/api/v1"
export BOAT_API_KEY="sandbox_your_secret_here"
```

Create a one-hour sandbox and store its id:

```bash theme={null}
BOAT_ID=$(curl -sS -X POST "$BOAT_API_BASE/sandboxes" \
  -H "Authorization: Bearer $BOAT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ttlSeconds":3600}' | jq -r '.sandbox.id')
```

Create a sandbox without automatic archival:

```bash theme={null}
curl -sS -X POST "$BOAT_API_BASE/sandboxes" \
  -H "Authorization: Bearer $BOAT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ttlSeconds":null}'
```

Poll readiness:

```bash theme={null}
curl -sS "$BOAT_API_BASE/sandboxes/$BOAT_ID" \
  -H "Authorization: Bearer $BOAT_API_KEY"
```

Prompt a sandbox to work in a repo:

```bash theme={null}
curl -sS -X POST "$BOAT_API_BASE/sandboxes/$BOAT_ID/prompt" \
  -H "Authorization: Bearer $BOAT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "codex",
    "model": "gpt-5.4",
    "reasoningEffort": "medium",
    "prompt": "Work on the selected repo: run tests, fix failures, commit the result, and report the preview URL if you start a server."
  }'
```

Read Sandbox work and lifecycle events:

```bash theme={null}
curl -sS "$BOAT_API_BASE/sandboxes/$BOAT_ID/events" \
  -H "Authorization: Bearer $BOAT_API_KEY"
```

Get a desktop streaming URL for live inspection:

```bash theme={null}
curl -sS -X POST "$BOAT_API_BASE/sandboxes/$BOAT_ID/desktop?vnc=1" \
  -H "Authorization: Bearer $BOAT_API_KEY"
```

To return a VNC URL that does not require an access token, send:

```bash theme={null}
curl -sS -X POST "$BOAT_API_BASE/sandboxes/$BOAT_ID/desktop?vnc=1" \
  -H "Authorization: Bearer $BOAT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"publicAccess":true}'
```

Stop/archive a sandbox when the workflow is complete:

```bash theme={null}
curl -sS -X POST "$BOAT_API_BASE/sandboxes/$BOAT_ID/stop" \
  -H "Authorization: Bearer $BOAT_API_KEY"
```

Archive keeps snapshots for resume. Permanent delete requires the exact target id as a confirmation header, returns `202`, and cannot be canceled:

```bash theme={null}
curl -sS -X DELETE "$BOAT_API_BASE/sandboxes/$BOAT_ID" \
  -H "Authorization: Bearer $BOAT_API_KEY" \
  -H "X-Ascii-Confirm-Delete: $BOAT_ID"
```

Poll `GET /deletion-operations/{operationId}` until `status` is `completed`. See [Data retention and deletion](/data-retention).

## Endpoint reference

| Endpoint                                        | Reference                                                                                                          |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `GET /me`                                       | [Get current Boat user](/api/reference/account/get-current-boat-user)                                              |
| `GET /limits`                                   | [Get Boat limits](/api/reference/account/get-boat-limits)                                                          |
| `GET /account/data-retention`                   | [Get account data-retention policy](/api/reference/account/get-account-data-retention-policy)                      |
| `PATCH /account/data-retention`                 | [Update account data-retention policy](/api/reference/account/update-account-data-retention-policy)                |
| `GET /deletion-operations/{operationId}`        | [Get deletion operation](/api/reference/account/get-deletion-operation)                                            |
| `GET /repos`                                    | [List GitHub repositories available to Boat](/api/reference/account/list-github-repositories-available-to-sandbox) |
| `POST /repos`                                   | [Select repository for sandboxes](/api/reference/account/select-repository-for-sandboxes)                          |
| `GET /api-keys`                                 | [List API keys](/api/reference/account/list-api-keys)                                                              |
| `GET /api-keys/{apiKeyId}/usage`                | [Get API key usage](/api/reference/account/get-api-key-usage)                                                      |
| `GET /secrets`                                  | [Get Boat secrets setup](/api/reference/account/get-boat-secrets-setup)                                            |
| `POST /secrets`                                 | [Update Boat secrets setup](/api/reference/account/update-boat-secrets-setup)                                      |
| `GET /sandboxes`                                | [List sandboxes](/api/reference/sandboxes/list-sandboxes)                                                          |
| `POST /sandboxes`                               | [Create sandbox](/api/reference/sandboxes/create-sandbox)                                                          |
| `GET /sandboxes/{sandboxId}`                    | [Get sandbox](/api/reference/sandboxes/get-sandbox)                                                                |
| `GET /sandboxes/{sandboxId}/usage`              | [Get boat usage](/api/reference/sandboxes/get-sandbox-usage)                                                       |
| `PATCH /sandboxes/{sandboxId}`                  | [Update sandbox](/api/reference/sandboxes/update-sandbox)                                                          |
| `DELETE /sandboxes/{sandboxId}`                 | [Permanently delete sandbox data](/api/reference/sandboxes/permanently-delete-sandbox-data)                        |
| `POST /sandboxes/{sandboxId}/stop`              | [Stop and archive sandbox](/api/reference/sandboxes/stop-and-archive-sandbox)                                      |
| `POST /sandboxes/{sandboxId}/resume`            | [Resume sandbox](/api/reference/sandboxes/resume-sandbox)                                                          |
| `POST /sandboxes/{sandboxId}/fork`              | [Fork sandbox](/api/reference/sandboxes/fork-sandbox)                                                              |
| `POST /sandboxes/{sandboxId}/prompt`            | [Prompt Sandbox](/api/reference/agent/prompt-sandbox-agent)                                                        |
| `GET /sandboxes/{sandboxId}/events`             | [List boat events](/api/reference/agent/list-sandbox-events)                                                       |
| `GET /sandboxes/{sandboxId}/prompts/{promptId}` | [Get prompt run status](/api/reference/agent/get-prompt-run-status)                                                |
| `GET /sandboxes/{sandboxId}/files`              | [Read a file from a sandbox](/api/reference/agent/read-sandbox-file)                                               |
| `PUT /sandboxes/{sandboxId}/files`              | [Write a file in a sandbox](/api/reference/agent/write-sandbox-file)                                               |
| `POST /sandboxes/{sandboxId}/commands`          | [Execute a command in a sandbox](/api/reference/agent/execute-sandbox-command)                                     |
| `GET /sandboxes/{sandboxId}/artifacts`          | [Download a sandbox artifact](/api/reference/agent/download-sandbox-artifact)                                      |
| `POST /sandboxes/{sandboxId}/interrupt`         | [Interrupt running work](/api/reference/agent/interrupt-running-agent-work)                                        |
| `POST /sandboxes/{sandboxId}/desktop`           | [Get desktop streaming URL](/api/reference/agent/get-desktop-streaming-url)                                        |
| `POST /sandboxes/{sandboxId}/sshkey`            | [Configure sandbox SSH key](/api/reference/agent/configure-sandbox-ssh-key)                                        |
| `GET /snapshots`                                | [List snapshots](/api/reference/snapshots/list-snapshots)                                                          |
| `GET /sandboxes/{sandboxId}/snapshots`          | [List boat snapshots](/api/reference/snapshots/list-sandbox-snapshots)                                             |
| `GET /sandboxes/{sandboxId}/snapshots/latest`   | [Get latest boat snapshot](/api/reference/snapshots/get-latest-sandbox-snapshot)                                   |
| `GET /snapshots/{snapshotId}/tree`              | [Get snapshot file tree](/api/reference/snapshots/get-snapshot-tree)                                               |
| `GET /snapshots/{snapshotId}/files`             | [Download a file or folder from a snapshot](/api/reference/snapshots/get-snapshot-file)                            |
| `GET /snapshots/{snapshotId}/download`          | [Get snapshot download](/api/reference/snapshots/get-snapshot-download)                                            |
| `DELETE /snapshots/{snapshotId}`                | [Permanently delete snapshot data](/api/reference/snapshots/permanently-delete-snapshot-data)                      |
