Docs / Hotcell / REST API

REST API reference

Every Hotcell daemon endpoint: auth, sandboxes, exec over SSE, files, processes, ports, sessions, code contexts, backups, metrics and egress tokens.

The daemon serves JSON over HTTP on http://127.0.0.1:4750 by default. Request and response bodies are JSON unless noted. Streaming endpoints use Server-Sent Events.

Authentication

Every request needs the API key, except GET /healthz, the dashboard HTML (/ and /dashboard) and GET /info. Send it as either header:

Authorization: Bearer <key>
X-API-Key: <key>

The key is HOTCELL_API_KEY, or the one the daemon generates in ${HOTCELL_HOME:-~/.hotcell}/api-key (mode 0600) when that variable is unset.

curl -s http://127.0.0.1:4750/sandboxes -H "Authorization: Bearer $(cat ~/.hotcell/api-key)"

Other request checks:

  • Host guard. When the daemon binds to loopback, requests whose Host header isn't a loopback name, HOTCELL_HOST or an entry in HOTCELL_ALLOWED_HOSTS get 403.
  • Cross-site guard. Browser requests marked cross-site (Sec-Fetch-Site or a foreign Origin) get 403.
  • Body size. Bodies over HOTCELL_MAX_BODY_BYTES (default 32 MiB) get 413.

Status codes

Code Meaning
200, 201 Success.
400 Invalid input (missing field, unknown driver, bad policy).
401 Missing or invalid API key.
403 Host or cross-site guard.
404 Unknown sandbox, process, session, context, backup, token or route.
409 Sandbox is creating, error or stopped for an operation that needs it live.
413 Body too large.
422 Provisioning failed during a blocking create.
503 Admission refused (host memory budget exhausted), with Retry-After: 5. Also /healthz when the driver is unreachable.

Errors are { "error": "<message>" }.

Daemon

Method Path Description
GET /healthz { ok, driver, version }; 503 if the driver ping fails.
GET /info Version, driver, drivers, defaultImage, proxyPort, egressPort, egressProviders, cost rates, defaultSleepAfterMs, auth, otlp, default limits.
GET /capacity Admission snapshot: memory, cpu, running, fits, pool, and boot (microVM boot queue).
GET /traces?limit=N Recent finished spans, newest first (default 100).
POST /reload-keys Reload provider keys without a restart. The CLI calls this after hotcell keys changes.
GET /, /dashboard The web dashboard.

Sandboxes

Method Path Description
POST /sandboxes Create a sandbox. Returns 201 with the record.
GET /sandboxes { sandboxes: [...] }.
GET /sandboxes/:id The sandbox record.
DELETE /sandboxes/:id Destroy, including its volume, sessions and egress tokens. Allowed in any state.
POST /sandboxes/:id/stop Stop.
POST /sandboxes/:id/start Start or resume. Admission-gated.
POST /sandboxes/:id/pause Pause (memory snapshot on microVM drivers).

Create body

Field Type Description
image string Default HOTCELL_IMAGE.
driver string container, firecracker or applevz. Default HOTCELL_DRIVER.
env object Sandbox env.
labels object Labels.
persist boolean Default true.
sleepAfter number Idle auto-pause in ms. Default HOTCELL_SLEEP_AFTER_MS.
setup string[] Commands run once after start.
repo string Git URL cloned into /workspace.
repoRef string Branch or tag to check out.
branch string New branch after clone; auto generates one.
memoryMb, cpus, pidsLimit number Hard caps. Default to the daemon defaults. memoryMb must be at least 6.
egress boolean or object true, or an egress policy object.
egressSpendCapUsd number Per-sandbox LLM spend ceiling.
detach boolean Respond immediately with status creating; poll GET /sandboxes/:id.
networked boolean Firecracker: NAT network interface.
writableRootfs boolean Firecracker: private writable rootfs without a network device.
cpuset string Firecracker: pin the VMM to a CPU list such as "0-7".

The record includes id, image, status, statusReason, createdAt, driver, labels, env, persist, lastActivityAt, sleepAfterMs, limits, egressSpendCapUsd and usage.

Exec and terminal

Method Path Description
POST /sandboxes/:id/exec Body { command, cwd?, env?, sessionId? }. SSE stream of stdout, stderr and exit events.
GET (WebSocket) /sandboxes/:id/terminal?cols=&rows=&key= Interactive PTY. Binary frames are stdin and output; text frames are JSON control messages ({"type":"resize","cols":N,"rows":N}).

Exec frames:

data: {"type":"stdout","data":"..."}

data: {"type":"stderr","data":"..."}

data: {"type":"exit","exitCode":0}

The stream carries an SSE comment (: hb) every 15 seconds while idle.

Files

Method Path Body Response
POST /sandboxes/:id/files/write { path, content, mode? } { ok: true }
POST /sandboxes/:id/files/read { path } { content }
POST /sandboxes/:id/files/mkdir { path, parents? } { ok: true }
POST /sandboxes/:id/files/list { path } { entries: [{ path, name, isDirectory, size, modifiedAt }] }
GET /sandboxes/:id/watch?path=&interval= SSE of { type, path } where type is created, modified or deleted. Default path /workspace.

mode is a 3- or 4-digit octal string.

Processes and ports

Method Path Description
POST /sandboxes/:id/processes Body { command, cwd?, env? }. Returns 201 with a process handle.
GET /sandboxes/:id/processes { processes: [...] }.
DELETE /sandboxes/:id/processes/:procId Body { signal? }. Default SIGTERM.
GET /sandboxes/:id/processes/:procId/logs?follow=1 SSE of { type: "log", data }, ending with { type: "end" }.
POST /sandboxes/:id/wait-port Body { port, timeoutMs?, intervalMs?, host? }. Returns { ready }.
POST /sandboxes/:id/expose Body { port, token? }. Returns 201 with { port, exposeId, token, createdAt, url }.
GET /sandboxes/:id/expose { exposed: [...] }.
DELETE /sandboxes/:id/expose/:port Remove an exposed port.

Sessions and environment

Method Path Description
POST /sandboxes/:id/sessions Body { id?, cwd?, env? }. Default cwd /workspace. 409 if the id exists.
GET /sandboxes/:id/sessions { sessions: [...] }.
POST /sandboxes/:id/sessions/:sessionId/env Body { env }; merged.
DELETE /sandboxes/:id/sessions/:sessionId Delete a session.
GET /sandboxes/:id/env { env }.
POST /sandboxes/:id/env Body { env }; merged into the sandbox env.

Code interpreter

Method Path Description
POST /sandboxes/:id/code-contexts Body { language? } (python default, or javascript). Returns 201 with { contextId, language, createdAt }.
GET /sandboxes/:id/code-contexts { contexts: [...] }.
DELETE /sandboxes/:id/code-contexts/:contextId Destroy a context and its kernel.
POST /sandboxes/:id/run-code Body { code, contextId?, language?, timeoutMs? } (default timeout 30000 ms). Returns { stdout, stderr, results, error }.

Backups

Method Path Description
POST /sandboxes/:id/backups Snapshot /workspace. Returns 201 with { backupId, sandboxId, createdAt, bytes }.
GET /sandboxes/:id/backups This sandbox's backups.
POST /sandboxes/:id/restore Body { backupId }. Replaces /workspace.
GET /backups All backups.
DELETE /backups/:backupId Delete a backup.

Metrics

Method Path Description
GET /sandboxes/:id/metrics?live=0 { status, live, usage, cost }. live is present only while running; live=0 skips the live snapshot. Doesn't count as activity.
GET /sandboxes/:id/metrics/history { samples: [{ at, cpuPercent, memBytes, netRxBytes, netTxBytes, pids }] }.

Egress tokens

Method Path Description
POST /sandboxes/:id/egress-tokens Body is an optional policy: ttlMs, expiresAt, spendCapUsd, rateLimit: { calls?, tokens?, windowMs }, models, providers. Returns 201 with { token, policy, providers }.
GET /sandboxes/:id/egress-tokens { tokens: [{ token, policy, spendUsd, spendRemaining }], providers }.
DELETE /sandboxes/:id/egress-tokens/:token Revoke.

See Egress for the gateway itself, which runs on a separate port.

Auto-resume

Operations that need a live sandbox (exec, files, processes, ports, code, backups, terminal) resume a paused sandbox first. If the resume doesn't fit the host budget, they return 503. A stopped sandbox returns 409 until you call /start.