# Migrate from boat.dev

The `TaskCloud` REST API follows the resource model of boat.dev's API (sandboxes, commands, files, hosted ports, snapshots, webhooks), so most code ports directly. The main differences:

- Field names are `snake_case`.
- "Stop" becomes **pause**, and pause keeps RAM.
- Idle sandboxes pause automatically (default after 5 min) and wake on request, so you pay for active seconds, not for the time until an auto-stop timer fires.

> **boat.dev compatibility adapter: Coming soon.** Until it ships, use the mapping below. The boat.dev details here come from its public documentation as of 2026-11; check boat.dev's current docs for exact behavior.

## Key differences

| | boat.dev | TaskCloud |
| --- | --- | --- |
| Base URL | `https://boat.dev/api/v1` | `https://api.task.cloud/v1` |
| JSON style | camelCase, `{"ok": true, "type": ...}` envelope | snake_case, plain resource objects |
| Errors | `{"ok": false, "code", "message", "error": {...}, "requestId"}` | `{"error": {"code", "message", "retryable", "status", "request_id", "details"}}` |
| Stop / resume | Stop saves a disk snapshot; resume boots again | **Pause** snapshots RAM and disk; resume restores the running state (target p95 < 800 ms) |
| Auto-stop | TTL-based auto-stop | **Auto-pause when idle** (default 5 min) plus **wake on request** (HTTP, SSH, API) |
| Fork | From the last disk snapshot | From the live VM **including memory** (target < 2 s) |
| Billing | Per second while the VM runs (until you stop it or its auto-stop timer fires) | Per second while running; **$0 compute while paused** |
| Delete | Requires the `X-Ascii-Confirm-Delete` header | Plain `DELETE` (no confirmation header) |
| Idempotency | `idempotencyKey` field | `Idempotency-Key` header |
| Rate-limit headers | `RateLimit-Policy` / `RateLimit` | Same headers (IETF draft), with policies `default` and `starts` |

## Endpoint mapping

| boat.dev | TaskCloud | Notes |
| --- | --- | --- |
| `POST /sandboxes` (`type`, `ttlSeconds`, `from`, `hostedPorts`) | `POST /sandboxes` (`size`, `ttl_seconds`, `template` or `snapshot_id`, `ports`) | `small` / `default` / `large` map directly. `xlarge` has no equivalent yet. |
| `GET /sandboxes`, `GET /sandboxes/{id}` | same paths | Filters: `state`, `label=key=value`, `name_prefix` |
| `PATCH /sandboxes/{id}` (extend) | `PATCH /sandboxes/{sandbox_id}` | `ttl_seconds`, `auto_pause`, `always_on` |
| `POST /sandboxes/{id}/stop` | `POST /sandboxes/{sandbox_id}/pause` | RAM is kept |
| `POST /sandboxes/{id}/resume` | `POST /sandboxes/{sandbox_id}/resume` | Usually not needed: requests wake the sandbox automatically. The size can't change on resume. |
| `POST /sandboxes/{id}/fork` | `POST /sandboxes/{sandbox_id}/fork` | Includes memory. Same size as the source. Bulk fork: `count` up to 50 per call, all or nothing. |
| `DELETE /sandboxes/{id}` | `DELETE /sandboxes/{sandbox_id}` | Returns `202` |
| `POST /sandboxes/{id}/commands` (`command`, `timeoutSeconds`, `detached`, `stream`) | `POST /sandboxes/{sandbox_id}/exec` (`command`, `timeout_seconds`, `background`, `stream`) | Sync max 600 s. Background has no limit. Stream is SSE. |
| `GET /sandboxes/{id}/commands/{processId}` and `.../stream` | `GET .../commands/{command_id}` and `.../stream` | Kill: `POST .../commands/{command_id}/kill` |
| `GET` / `PUT` / `DELETE /sandboxes/{id}/files` | `GET` / `PUT /sandboxes/{sandbox_id}/files/content`, `DELETE /sandboxes/{sandbox_id}/files` | Read limit 1 GiB per request (boat.dev: 5 MiB) |
| `GET .../files/list`, `.../files/info` | `GET .../files`, `GET .../files/stat` | |
| `POST .../files/folder`, `.../files/move` | `POST .../files/mkdir`, `.../files/move` | |
| `POST .../files/upload-url`, `.../download-url` | same paths | Up to 10 GiB; URLs valid 15 min by default, 1 h max |
| `POST /sandboxes/{id}/host` (`port`, `public`) | `POST /sandboxes/{sandbox_id}/ports` (`port`, `visibility`) | URL `https://<short_id>-<port>.taskhost.cloud` |
| `POST /sandboxes/{id}/desktop` | `POST /sandboxes/{sandbox_id}/desktop` | KasmVNC signed URL with `ttl_seconds`, `read_only`. Screenshot and input endpoints are also available. |
| `POST /sandboxes/{id}/sshkey` | `POST /sandboxes/{sandbox_id}/ssh` or account-wide `POST /ssh-keys` | Gateway `ssh.taskhost.cloud` |
| `GET` / `POST /saved-snapshots` | `POST /sandboxes/{sandbox_id}/snapshots`, `GET /snapshots` | Turn a snapshot into a reusable template with `POST /templates` |
| `GET /sandboxes/{id}/usage` | `GET /sandboxes/{sandbox_id}/usage` | Also account-wide: `GET /usage?group_by=sandbox` |
| `GET /limits` | `GET /limits` | |
| `GET` / `POST /api-keys`, `POST /api-keys/scoped` | `GET` / `POST /api-keys` | Presets `read-only`, `full-sandbox`, `ci`, `admin` are kept; restrict a key with `restrict.sandbox_ids` / `restrict.labels` |
| `GET` / `POST /webhooks`, `POST /webhooks/{id}/rotate` | `GET` / `POST /webhooks`, `POST /webhooks/{webhook_id}/rotate-secret` | See below |

## Webhooks

| boat.dev event | TaskCloud event |
| --- | --- |
| `sandbox.ready` | `sandbox.ready` (new sandbox) and `sandbox.resumed` (after pause) |
| `sandbox.error` | `sandbox.error` |
| `sandbox.archived` (stopped) | `sandbox.paused` |
| — | `sandbox.deleted` |
| `sandbox.hydrated`, `sandbox.degraded`, `sandbox.recovered` | No equivalent. Resume restores memory and disk together, so there is no separate hydration step. |

The signature scheme is the same shape: hex `HMAC-SHA256(secret, id + "." + timestamp + "." + raw_body)`, prefixed `v1=`, with a 300 s window. Only the header names change:

| boat.dev | TaskCloud |
| --- | --- |
| `X-Ascii-Delivery` | `Webhook-Id` |
| `X-Ascii-Timestamp` | `Webhook-Timestamp` |
| `X-Ascii-Signature` | `Webhook-Signature` |
| `X-Ascii-Event` | `Webhook-Event` |
| `X-Ascii-Attempt` | `Webhook-Attempt` |

The payload uses `created_at`, `previous_state` and `state` (instead of camelCase), and adds `reason`. See [Webhooks](webhooks.md).

## CLI mapping

| boat | taskcloud |
| --- | --- |
| `boat login` | `taskcloud login` |
| `boat new --type default` | `taskcloud create --size default` |
| `boat new --from <snapshot>` | `taskcloud create --snapshot <snapshot_id>` or `--template <name>` |
| `boat list` / `boat info <id>` | `taskcloud list` / `taskcloud get <id>` |
| `boat exec <id> -- npm test` | `taskcloud exec <id> -- npm test` |
| `boat ssh <id>` | `taskcloud ssh <id>` |
| `boat stop <id>` | `taskcloud pause <id>` |
| `boat resume <id>` | `taskcloud resume <id>` |
| `boat fork <id>` | `taskcloud fork <id>` |
| `boat host <id> 3000 --public` | `taskcloud port expose <id> 3000 --public` |
| `boat desktop <id>` | `taskcloud desktop <id> --open` |
| `boat delete <id>` | `taskcloud delete <id>` |

See [CLI](../cli.md) for the full reference.

## Example: create, run, stop → create, run, (auto-)pause

```bash
# boat.dev
curl -X POST https://boat.dev/api/v1/sandboxes -H "Authorization: Bearer $BOAT_API_KEY" \
  -H "Content-Type: application/json" -d '{"ttlSeconds":3600}'

# TaskCloud
curl -X POST https://api.task.cloud/v1/sandboxes -H "Authorization: Bearer $TASKCLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template":"base","size":"default","auto_pause":{"enabled":true,"idle_timeout_seconds":300}}'

curl -X POST https://api.task.cloud/v1/sandboxes/$SANDBOX_ID/exec -H "Authorization: Bearer $TASKCLOUD_API_KEY" \
  -H "Content-Type: application/json" -d '{"command":"echo hello","timeout_seconds":30}'
# No explicit stop needed: after 5 idle minutes the sandbox pauses; the next request wakes it.
```

## Not available (yet)

| boat.dev feature | Status on TaskCloud |
| --- | --- |
| Built-in agent harnesses (`boat prompt`, conversations, steer/interrupt) | Not planned. We are infrastructure: run Claude Code, Codex or your own agent inside the sandbox. |
| AI credential profiles and AI key proxy | Roadmap |
| Environments with repositories, setup scripts and secrets | Use [templates](../concepts/templates.md) (from an image or snapshot) plus `env` at creation |
| Nested virtualization (`/dev/kvm`) | Roadmap (separate class on Cloud Hypervisor) |
| OpenTelemetry export | Not in the MVP. Use webhooks and the usage API. |
| `xlarge` size | Not available. The largest size is `large` (8 vCPU / 16 GB). |

## Cost comparison

Hourly price for 4 vCPU / 8 GB, according to battleships.dev as of 2026-10: boat.dev **$0.036** per running hour. `TaskCloud` `default` is **$0.030** per active hour, and $0 compute while paused.

The list prices are close, so the difference comes from how long a VM stays billed after the work stops:

- boat.dev stops a VM on a timer you set (default 1 h). Until the timer fires, the VM is running and billed, and resuming it boots the machine again.
- `TaskCloud` pauses an idle VM after 5 minutes by default, keeps its RAM, and resumes it on the next request.

How much that saves depends on your session pattern and auto-stop setting. Paste a usage export into the [bill calculator](https://task.cloud/bill-calculator.html) to see both bills side by side.

See [Billing](../billing.md) for how active time is measured.
