> ## 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 Boat 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](/box/sdks/overview), [Python SDK](/box/sdks/python), or [TypeScript/JavaScript SDK](/box/sdks/typescript).

## Recommended flow

1. Create a Boat API key with `box api-key create` or in the dashboard.
2. Create a box with `POST /boxes`, or resume/fork an existing box when the user is continuing prior work.
3. Poll `GET /boxes/{boxId}` until the box is `ready` or `idle`.
4. Queue work with `POST /boxes/{boxId}/prompt`.
5. Read `GET /boxes/{boxId}/events` while work is running.
6. Use `POST /boxes/{boxId}/desktop` when the user or your support team needs live computer-use visibility.
7. Stop/archive, resume, fork, or delete the box 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-box tools like `host`, package installs, or your own scripts, is driven through [`POST /boxes/{boxId}/commands`](/box/api/reference/agent/execute-box-command). For high-frequency or streaming control, run your own daemon in the box; see [the daemon pattern](/box/platform-guide#bring-your-own-harness-the-daemon-pattern).

## Base URL

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

## Authentication

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

```bash theme={null}
curl -sS "https://ascii.dev/api/box/v1/boxes" \
  -H "Authorization: Bearer $BOX_API_KEY"
```

Create, rotate, and revoke service keys with the `box api-key` CLI command or in the Boat dashboard. For key lifecycle guidance, see [API Keys](/box/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 Boat session and refuses API keys. Per-Boat keys are restricted to their Boat; 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": "box.list",
  "boxes": []
}
```

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

## Error model

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

```json theme={null}
{
  "ok": false,
  "type": "box.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://box.ascii.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 box machine to start, or retry once the box 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 box or key no longer exists.                                                     |
| `409`  | `account_not_ready`, `provider_not_configured`, `box_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 box creation

`POST /boxes` and `POST /boxes/{boxId}/fork` each provision a new box that bills on success, so a lost response (timeout or `5xx` after the box 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 box and binds it to the key; every retry with the **same account, key, and request body** returns that same box instead of a second, billable one.

```bash theme={null}
curl -sS -X POST "$BOX_API_BASE/boxes" \
  -H "Authorization: Bearer $BOX_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 box (no second box).                  |
| Retry while the first box 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 box existed        | Key auto-releases within \~2 minutes so a retry can create the box. |

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 box](/box/api/reference/boxes/create-box) and [Fork box](/box/api/reference/boxes/fork-box).

## Boat lifecycle

A Boat moves through these states:

| State                                    | Meaning                                              | Client behavior                                                             |
| ---------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------- |
| `provisioning`, `provisioned`, `cloning` | The sandbox is being created or prepared.            | Poll `GET /boxes/{boxId}`.                                                  |
| `ready`, `idle`                          | The box can accept prompts and interactive access.   | Prompt, open desktop, SSH, or inspect events.                               |
| `running`                                | The Boat 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 /boxes/{boxId}/prompt`. Processes you run yourself, over SSH or the command endpoint, do not change the state: a box can show `idle` while your own agent works inside it.
</Note>

## Providers, models, and reasoning

`POST /boxes/{boxId}/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 `box 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 Box 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](/box/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](/box/snapshots), [Environments](/box/environments), [Webhooks](/box/webhooks), [Data retention](/box/data-retention), [Desktop](/box/desktop-streaming), [Hosting](/box/hosting). The [CLI reference](/box/cli-reference) maps commands to endpoints.

Set shared variables:

```bash theme={null}
export BOX_API_BASE="https://ascii.dev/api/box/v1"
export BOX_API_KEY="box_your_secret_here"
```

Create a one-hour box and store its id:

```bash theme={null}
BOX_ID=$(curl -sS -X POST "$BOX_API_BASE/boxes" \
  -H "Authorization: Bearer $BOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ttlSeconds":3600}' | jq -r '.box.id')
```

Create a box without automatic archival:

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

Poll readiness:

```bash theme={null}
curl -sS "$BOX_API_BASE/boxes/$BOX_ID" \
  -H "Authorization: Bearer $BOX_API_KEY"
```

Prompt a Boat to work in a repo:

```bash theme={null}
curl -sS -X POST "$BOX_API_BASE/boxes/$BOX_ID/prompt" \
  -H "Authorization: Bearer $BOX_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 Boat work and lifecycle events:

```bash theme={null}
curl -sS "$BOX_API_BASE/boxes/$BOX_ID/events" \
  -H "Authorization: Bearer $BOX_API_KEY"
```

Get a desktop streaming URL for live inspection:

```bash theme={null}
curl -sS -X POST "$BOX_API_BASE/boxes/$BOX_ID/desktop?vnc=1" \
  -H "Authorization: Bearer $BOX_API_KEY"
```

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

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

Stop/archive a box when the workflow is complete:

```bash theme={null}
curl -sS -X POST "$BOX_API_BASE/boxes/$BOX_ID/stop" \
  -H "Authorization: Bearer $BOX_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 "$BOX_API_BASE/boxes/$BOX_ID" \
  -H "Authorization: Bearer $BOX_API_KEY" \
  -H "X-Ascii-Confirm-Delete: $BOX_ID"
```

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

## Endpoint reference

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