/ docs / the spawnd api

Sandboxes for agents.

spawnd gives an AI agent a secure Firecracker microVM over a REST call. Create a sandbox, run untrusted code inside it, expose a port, snapshot and fork it — and pay only for the resource-seconds you allocate.

Overview

The primary consumer of spawnd is a machine — an agent or app driving sandboxes at scale, not a human clicking around. A sandbox is one Firecracker microVM running a generic Ubuntu rootfs with common toolchains preinstalled (Node, Python, Go, Rust, and the usual build utilities). Guest code is hostile by default and isolated by construction; it cannot reach the host, the control plane, or another sandbox.

REST / SDK API

The primary surface. Create, exec, files, ports, and the full lifecycle.

SSH & terminal

A browser terminal or SSH into the VM for human sessions.

Preview URLs

Expose a guest port at <id>--<workspace>.spawnd.online via reverse proxy.

MCP server

Sandbox control as MCP tools, so LLM clients drive VMs directly.

Quickstart

From zero to a running sandbox with curl. Base URL is https://api.spawnd.sh.

  1. Request a magic link

    In dev mode the sign-in link is returned inline as devLink.

    curl -s -X POST https://api.spawnd.sh/auth/magic-link \
      -H 'Content-Type: application/json' \
      -d '{"email":"you@example.com"}'
    # → { "sent": true, "expiresInMinutes": 15, "devLink": "https://api.spawnd.sh/auth/verify?token=..." }
  2. Verify to get a session JWT

    curl -s 'https://api.spawnd.sh/auth/verify?token=THE_TOKEN'
    # → { "token": "<jwt>", "user": {...}, "org": {...} }
  3. Mint an org API key

    Session-authed. The full key is shown once — store it.

    curl -s -X POST https://api.spawnd.sh/v1/keys \
      -H 'Authorization: Bearer <jwt>' \
      -H 'Content-Type: application/json' \
      -d '{"name":"cli"}'
    # → { "key": "e2b_...", "keyRecord": { "prefix": "e2b_...", ... } }
  4. Create a sandbox and exec

    curl -s -X POST https://api.spawnd.sh/v1/sandboxes \
      -H 'Authorization: Bearer e2b_...' \
      -H 'Content-Type: application/json' -d '{"template":"base"}'
    # → { "id": "sbx_123", "state": "running", ... }
    
    
    curl -s -X POST https://api.spawnd.sh/v1/sandboxes/sbx_123/exec \
      -H 'Authorization: Bearer e2b_...' \
      -H 'Content-Type: application/json' \
      -d '{"cmd":"echo","args":["hello from the box"]}'
    # → { "exitCode": 0, "stdout": "hello from the box\n", "durationMs": 42 }
  5. Kill it when done

    Idempotent — billing stops the moment the VM is reaped.

    curl -s -X DELETE https://api.spawnd.sh/v1/sandboxes/sbx_123 \
      -H 'Authorization: Bearer e2b_...'
    # → { "ok": true }

Authentication

Two disjoint auth surfaces share one port. A session JWT cannot drive the sandbox SDK, and an API key cannot manage keys.

Org API key

All sandbox and usage calls. Sent as a bearer token:

Authorization: Bearer e2b_...

Stored as a sha256 hash + 20-char prefix; the secret is shown once at creation.

Session JWT

Dashboard and key-management calls (/auth/*, /v1/keys):

Authorization: Bearer <jwt>

Issued by the passwordless magic-link flow. No passwords anywhere.

Magic link, dev mode. With no email provider configured the sign-in link is returned in the response (devLink) and logged — never dropped. The console auto-provisions your default API key on first authenticated load.

Sandboxes & sizes

template accepts a named size preset. Allocation (vCPU + memory) is fixed at create time and is the basis for resource-seconds billing. Unknown names fall back to the base size.

NamevCPUMemoryUse for
smallbox / base12048 MiBLight scripts, quick execs — cheapest.
box / default24096 MiBThe default. General-purpose agent work.
bigbox48192 MiBHeavy compilation, parallel workloads.
megabox816384 MiBThe largest box — big builds, many parallel jobs.

Lifecycle

Every sandbox supports the full lifecycle, and every verb is idempotent: kill on a dead sandbox succeeds, pause on a paused VM is a no-op, and create with the same idempotency key returns the same sandbox.

VerbMeaning
createAllocate a sandbox from a template; resumes from the warm pool when it can.
startBring a created sandbox to running.
execRun a command inside the guest and get its stdout, stderr, and exit code.
snapshotCapture full VM state (memory + disk) as a restorable artifact.
forkCopy-on-write clone from a snapshot — cheap children from one parent state.
pauseSnapshot memory + state and freeze CPU; billing drops to the paused rate.
resumeUn-freeze a paused VM back to running.
stopGraceful shutdown.
killHard teardown; release every host resource immediately. Terminal.

Legal states: creating, running, paused, stopping, stopped, killed, plus transient snapshotting and forking. killed is terminal. Cold starts use snapshot-restore from a warm pool, so create and resume are sub-second.

state machine
                 create
   (none) ─────────────────▶ creating
                                 │ boot + guest ping ok
                                 ▼
     ┌──────────── pause ◀── running ──▶ stopping ──▶ stopped
     │                        ▲   │        (graceful)      │
   paused ── resume ──────────┘   │ snapshot               │ resume/start
     │                            ▼                        │
     │                     (snapshot taken, stays running) │
     └──────────────── kill ─────┴──────── kill ───────────┘ ──▶ killed (terminal)


   fork(snapshot) ─────────▶ creating (a NEW sandbox id)

Isolation

Guest code is treated as hostile. Each VM boots under jailer (chroot + dropped privileges) in its own network namespace, with a per-VM tap device and double-NAT'd outbound internet. From inside a sandbox there is no route to the host, no route to the control plane, and no route to another sandbox; /dev/kvm and host files are unreachable. Isolation is enforced by construction, not by trusting the guest.

Billing — resource-seconds

spawnd bills allocated resource-seconds, not flat wall-clock, so cost scales with size. Both dimensions derive from the create-time allocation × time:

vCPU-secondsvcpu × seconds
GiB-seconds(memMib / 1024) × seconds

Allocation is fixed at create, so billing is deterministic and unforgeable — the guest cannot change its own vcpu/memMib.

StateBilledRate class
runningvCPU-s + GiB-s, liveactive
snapshotting, forkinglive (still running)active
pausedvCPU-s + GiB-s, paused ratepaused
creatingnot billed until guest ping ok—
stopped, killednot billed—

API · Auth

The passwordless sign-in flow. No auth required to start it; the token is the credential.

POST/auth/magic-linknone

Find-or-create the org + user, mint a single-use token, and send the sign-in link. devLink is present only in dev mode.

Request
{ "email": string }
Response
{ "sent": boolean, "email": string, "expiresInMinutes": number, "devLink"?: string }
curl -s -X POST https://api.spawnd.sh/auth/magic-link \
  -H 'Content-Type: application/json' \
  -d '{"email":"you@example.com"}'
GET/auth/verify?token=the token itself

Consume the single-use token (unexpired, unused) and issue a session JWT. Invalid, used, or expired → 401.

Response
{ "token": string, "user": {...}, "org": {...} }
curl -s 'https://api.spawnd.sh/auth/verify?token=THE_TOKEN'
GET/auth/mesession JWT

Return the current profile for a session. Missing, invalid, or expired token → 401.

Response
{ "user": {...}, "org": {...} }
curl -s https://api.spawnd.sh/auth/me \
  -H 'Authorization: Bearer <jwt>'

API · Keys

Session-authed key management. The console provisions your first key automatically.

GET/v1/keyssession JWT

List the org's keys. The secret is never returned.

Response
ApiKey[] // { id, prefix, name, createdAt, lastUsedAt, revokedAt }
curl -s https://api.spawnd.sh/v1/keys \
  -H 'Authorization: Bearer <jwt>'
POST/v1/keyssession JWT

Mint a new e2b_ key. The full key is returned exactly once.

Request
{ "name"?: string }
Response
{ "key": string, "keyRecord": ApiKey }
curl -s -X POST https://api.spawnd.sh/v1/keys \
  -H 'Authorization: Bearer <jwt>' \
  -H 'Content-Type: application/json' \
  -d '{"name":"ci"}'

API · Sandboxes

Authenticated with an org API key and scoped to the caller's org. A sandbox from another org returns 404.

POST/v1/sandboxesAPI key

Allocate and start a sandbox. Resumes from the warm pool when possible. template is a named size; unknown names fall back to base.

Request
{ "template": string, "metadata"?: object, "idempotencyKey"?: string }
Response
Sandbox
curl -s -X POST https://api.spawnd.sh/v1/sandboxes \
  -H 'Authorization: Bearer e2b_...' \
  -H 'Content-Type: application/json' \
  -d '{"template":"bigbox","metadata":{"job":"build-42"}}'
GET/v1/sandboxesAPI key

List the caller's org sandboxes, newest first.

Response
Sandbox[]
curl -s https://api.spawnd.sh/v1/sandboxes \
  -H 'Authorization: Bearer e2b_...'
GET/v1/sandboxes/:idAPI key

Get one sandbox's current state.

Response
Sandbox
curl -s https://api.spawnd.sh/v1/sandboxes/sbx_123 \
  -H 'Authorization: Bearer e2b_...'
POST/v1/sandboxes/:id/execAPI key

Run a command inside the guest over vsock and return its result.

Request
{ "cmd": string, "args"?: string[], "cwd"?: string, "env"?: object, "timeoutMs"?: number }
Response
{ "exitCode": number, "stdout": string, "stderr": string, "durationMs": number }
curl -s -X POST https://api.spawnd.sh/v1/sandboxes/sbx_123/exec \
  -H 'Authorization: Bearer e2b_...' \
  -H 'Content-Type: application/json' \
  -d '{"cmd":"python3","args":["-c","print(2+2)"]}'
POST/v1/sandboxes/:id/filesAPI key

Write a file into the guest. Contents are base64-encoded.

Request
{ "path": string, "contentB64": string }
Response
{ "bytes": number }
curl -s -X POST https://api.spawnd.sh/v1/sandboxes/sbx_123/files \
  -H 'Authorization: Bearer e2b_...' \
  -H 'Content-Type: application/json' \
  -d '{"path":"/root/app.py","contentB64":"cHJpbnQoMSk="}'
GET/v1/sandboxes/:id/files?path=API key

Read a file from the guest. Contents come back base64-encoded.

Response
{ "contentB64": string }
curl -s 'https://api.spawnd.sh/v1/sandboxes/sbx_123/files?path=/root/app.py' \
  -H 'Authorization: Bearer e2b_...'
POST/v1/sandboxes/:id/portsAPI key

Expose a guest port publicly. Returns a preview URL at <id>--<workspace>.spawnd.online.

Request
{ "port": number }
Response
{ "url": string }
curl -s -X POST https://api.spawnd.sh/v1/sandboxes/sbx_123/ports \
  -H 'Authorization: Bearer e2b_...' \
  -H 'Content-Type: application/json' \
  -d '{"port":8000}'
POST/v1/sandboxes/:id/pauseAPI key

Snapshot and freeze the VM. Billing drops to the paused rate; memory stays resident.

Response
Sandbox
curl -s -X POST https://api.spawnd.sh/v1/sandboxes/sbx_123/pause \
  -H 'Authorization: Bearer e2b_...'
POST/v1/sandboxes/:id/resumeAPI key

Restore a paused VM back to running from its pause snapshot.

Response
Sandbox
curl -s -X POST https://api.spawnd.sh/v1/sandboxes/sbx_123/resume \
  -H 'Authorization: Bearer e2b_...'
POST/v1/sandboxes/:id/snapshotAPI key

Take a named, restorable full-state snapshot (memory + disk). The VM stays running.

Request
{ "name"?: string }
Response
Snapshot
curl -s -X POST https://api.spawnd.sh/v1/sandboxes/sbx_123/snapshot \
  -H 'Authorization: Bearer e2b_...' \
  -H 'Content-Type: application/json' \
  -d '{"name":"after-deps"}'
POST/v1/sandboxes/:id/forkAPI key

Copy-on-write clone from a snapshot into a fresh sandbox (a new id). Omit snapshotId to fork the latest.

Request
{ "snapshotId"?: string }
Response
Sandbox // the new child
curl -s -X POST https://api.spawnd.sh/v1/sandboxes/sbx_123/fork \
  -H 'Authorization: Bearer e2b_...' \
  -H 'Content-Type: application/json' \
  -d '{"snapshotId":"snap_abc"}'
POST/v1/sandboxes/:id/stopAPI key

Gracefully shut down the sandbox.

Response
Sandbox
curl -s -X POST https://api.spawnd.sh/v1/sandboxes/sbx_123/stop \
  -H 'Authorization: Bearer e2b_...'
DELETE/v1/sandboxes/:idAPI key

Kill the sandbox and reap all host resources. Idempotent — killing a dead sandbox still returns ok.

Response
{ "ok": boolean }
curl -s -X DELETE https://api.spawnd.sh/v1/sandboxes/sbx_123 \
  -H 'Authorization: Bearer e2b_...'

API · Usage

Query the org's resource-seconds ledger over a time window.

GET/v1/usageAPI key

Return the org's resource-seconds (vCPU-s + GiB-s) over a time window.

Response
UsageSummary
curl -s 'https://api.spawnd.sh/v1/usage?from=2026-08-01T00:00:00Z&to=2026-08-07T00:00:00Z' \
  -H 'Authorization: Bearer e2b_...'

Errors

Every API error is { code, message, retryable }. Only capacity is generally worth retrying.

CodeMeaningHTTP
unauthorizedMissing, invalid, or wrong-surface credential401
not_foundNo such sandbox or resource for this org404
invalid_stateThe verb is illegal for the sandbox's current state409
quota_exceededOrg quota reached429
capacityNo host slot right now (retryable)503
guest_timeoutThe guest did not respond in time504
internalUnexpected server error500

CLI

The spawn CLI wraps the same REST API. Authenticate once with your org API key, then drive sandboxes from the terminal.

CommandDoes
spawn smallboxCreate and enter a smallbox (1 vCPU / 2048 MiB).
spawn bigboxCreate and enter a bigbox (4 vCPU / 8192 MiB).
spawn lsList your org's sandboxes.
spawn exec <id> -- <cmd>Run a command inside a sandbox and stream the result.
spawn ssh <id>A real SSH session — scp, rsync and Remote-SSH work against it.
spawn cp <src> <dst>Copy files in or out; one side is <id>:<path>.
spawn fork <id>Copy-on-write clone a sandbox from its latest snapshot.
spawn snapshotsList your org's durable snapshots; -fork, -rename, -rm act on one.
spawn rm <id>Kill a sandbox and reap its resources.
# create a bigbox, run a build, then tear it down
spawn bigbox
spawn exec sbx_123 -- cargo build --release
spawn rm sbx_123

MCP server

The orchestrator exposes sandbox control as an MCP server, so any MCP client — an IDE, an agent framework, an LLM app — can drive sandboxes as tools. Register it with your org API key as the bearer credential.

client registration
{
  "mcpServers": {
    "spawnd": {
      "url": "https://api.spawnd.sh/mcp",
      "headers": { "Authorization": "Bearer e2b_..." }
    }
  }
}
ToolDoes
spawn_sandboxCreate a sandbox from a size template.
execRun a command inside a sandbox.
write_fileWrite a file into the guest.
read_fileRead a file from the guest.
expose_portExpose a guest port and get a preview URL.
ssh_accessWhere sshd answers, for handing a human a way in.
snapshotCapture durable disk state, optionally named.
forkCopy-on-write clone from live state or a snapshot.
list_snapshotsThe org's durable snapshots — they outlive their sandbox.
fork_snapshotBoot a new sandbox from a snapshot, no live parent needed.
rename_snapshot / delete_snapshotRelabel or remove one.
pause / resumePause or resume a sandbox.
killKill a sandbox and reap resources.

Preview URLs

Expose a guest port to the public internet with POST /v1/sandboxes/:id/ports. The response url is http://<sandbox-id>--<workspace>.spawnd.online — a single DNS label (double-dash separator) so one *.spawnd.online wildcard covers every preview — routed by the HTTP Host header. Previews are org-scoped by construction and never outlive their VM — stopping or killing the sandbox deregisters the mapping.

# start a dev server in the sandbox, expose it, share the URL
spawn exec sbx_123 -- python3 -m http.server 8000 &
curl -s -X POST https://api.spawnd.sh/v1/sandboxes/sbx_123/ports \
  -H 'Authorization: Bearer e2b_...' \
  -H 'Content-Type: application/json' -d '{"port":8000}'
# → { "url": "http://sbx_123--acme.spawnd.online" }


# routed by Host header, so it is testable locally:
curl -H 'Host: sbx_123--acme.spawnd.online' http://127.0.0.1:8080/

SSH available

Add your public key first. Until an SSH key is authorized for your org, a sandbox reports sshReason: "no_org_key" instead of an endpoint — see Settings → SSH keys. That page is the only way in: it is session-authed on purpose, so an API key cannot authorize a permanent shell into every sandbox you own.

SSH is the human interface to a sandbox. Everything an agent needs is in the REST API; SSH exists so a person can work in a sandbox with the tools they already use — scp, rsync, git, and editor Remote-SSH — none of which speak our API. Each sandbox is single-tenant, so you log in as root.

1 · Add your public key, once

Paste ~/.ssh/id_ed25519.pub into Settings → SSH keys. The key is stored on your organization and authorizes every sandbox the org creates — there is nothing to configure per sandbox. No private key ever leaves your machine, and if you do not have a key yet:

ssh-keygen -t ed25519 -C "$(whoami)@$(hostname)"

2 · Connect

A sandbox with SSH wired reports an ssh object on GET /v1/sandboxes/:id, and the console shows the same command. The host is the sandbox's preview name; the port is allocated per sandbox and is stable for its lifetime, so it is safe to put in ~/.ssh/config.

ssh -p 34211 root@sbx_123--acme.spawnd.online


# file transfer, sync, and Remote-SSH all work — it is a real sshd
scp -P 34211 app.tar.gz root@sbx_123--acme.spawnd.online:/root/
rsync -e 'ssh -p 34211' -a ./src/ root@sbx_123--acme.spawnd.online:/root/src/
code --remote ssh-remote+sbx_123--acme.spawnd.online:34211 /root


# or let the CLI find the port for you (and resume the sandbox if it is paused)
spawn ssh sbx_123
spawn ssh-config sbx_123 >> ~/.ssh/config

Host keys

Every sandbox generates its own ed25519 host key on first boot, so two sandboxes are never the same host to your SSH client. The fingerprint is published on the sandbox — compare it on first connect, or let spawn ssh-config write the known_hosts entry for you. You should never need StrictHostKeyChecking=no; if a fingerprint changes on a sandbox you did not recreate, stop and tell us.

Behaviour worth knowing

SituationWhat happens
Org has no SSH keyNo public port is opened at all — the right default for orgs that only ever drive sandboxes from code
Session open past the idle timeoutSandbox stays running — a live SSH connection counts as activity, so a long build never freezes under you
Connecting to a paused sandboxConnection refused; nothing is listening to wake it. Resume first — spawn ssh does that for you
You revoke a keyImmediate for new sandboxes, and on next start for sandboxes already running
You fork a sandboxThe child gets its own port, but inherits its parent's host key from the snapshot — expect a matching fingerprint

Everything on this page is also available as one self-contained markdown file for agents to ingest.