E2B SDK compatibility surface

The “drop-in replacement” framing is approximately true for the common E2B SDK workflow (create → run code → close) and approximately false for anything observability-adjacent. The CubeAPI/README.md support matrix is the canonical source.

Implemented endpoints

From CubeAPI/README.md + cross-checked against CubeAPI/src/routes.rs:

MethodPathRole in common E2B flow
GET/healthreadiness probe
POST/sandboxesSandbox.create(template=…)
GET/sandboxesSandbox.list()
GET/v2/sandboxesv2 list w/ filters
GET/sandboxes/:idSandbox.get(id)
DELETE/sandboxes/:idsandbox.close()
POST/sandboxes/:id/pausesnapshot/pause for later resume
POST/sandboxes/:id/resume(deprecated; prefer connect)
POST/sandboxes/:id/connectattach client, auto-resume from pause
GET/events/sandboxesrecent lifecycle events
GET/events/sandboxes/:idlifecycle events for one sandbox
GET/POST/events/webhookslifecycle webhook list/create
GET/PATCH/DELETE/events/webhooks/:idlifecycle webhook inspect/update/delete
GET/events/webhooks/:id/deliverieswebhook delivery-attempt history
POST/sandboxes/:id/timeoutset the sandbox timeout window
POST/sandboxes/:id/refreshesrefresh the timeout window
GET/sandboxes/:id/logs (v1)per-sandbox log collection
GET/v2/sandboxes/:id/logsv2 log alias
GET/sandboxes/:id/metricsCPU/memory/disk gauges for one sandbox
PUT/sandboxes/:id/networklive egress policy update
POST/sandboxes/:id/snapshotsdurable ZFS snapshot creation
GET/sandboxes/snapshotslist durable snapshots
GET/templateslist jail templates (#68)
GET/templates/:namesingle template metadata (#68)
POST/templates/buildasync template import/build job
GET/templates/builds/:job_id/logsretained build output
GET/templates/builds/:job_id/eventsbuild-log SSE stream
GET/poolwarm-pool status per template (#68)
POST/pool/:template/warmpre-warm N sandboxes for a template (#68)
POST/pool/:template/drainrelease every warm entry for a template (#68)

This is the lifecycle. Sandbox.create → run_code → close only touches POST /sandboxes, the WebSocket connect, and DELETE /sandboxes/:id. All three are in the ✅ column.

Lifecycle compatibility

POST /sandboxes accepts timeoutMs and the E2B AutoResume lifecycle block. It also accepts the E2B SDK 2.20 top-level shape "autoPause": true, "autoResume": {"enabled": true}:

{
  "timeoutMs": 600000,
  "lifecycle": {
    "onTimeout": "pause",
    "autoResume": true
  }
}

For jail-backed sandboxes and bhyve SSH guests, the reaper pauses on timeout and envd/files/commands activity auto-resumes the sandbox before dispatch. Detail responses embed the lifecycle block (started_at, last_active_at, end_at, state, onTimeout, autoResume, timeoutMs) so newer SDKs and dashboards can render state without composing it manually. See /appendix/auto-suspend-resume.

GET /events/sandboxes and GET /events/sandboxes/:id expose the E2B-shaped event history for created, paused, resumed, updated, snapshotted, and killed sandboxes. Query parameters match E2B’s read API: limit, offset, orderAsc, and repeated types= filters. See /appendix/lifecycle-events.

/events/webhooks implements the companion webhook management shape: register a URL, enable it for selected lifecycle event types, sign each POST with E2B-compatible e2b-signature headers, retry failed attempts, and inspect recent delivery rows through /events/webhooks/:id/deliveries.

Still missing or intentionally partial

SurfaceStatus
Local IDE attach / Remote-SSHBrowser code-server is shipped; expiring local IDE credentials are shipped for SSH-backed bhyve guests.
Lifecycle event webhooksWire shape is shipped; persistence and replay are production-hardening work.
Per-team API keys, quotas, auditOptional key registry, coarse route scopes, create-path per-tenant active-sandbox quotas, and /audit/events are shipped; durable org/key management remains hardening work.
bhyve pause/resume through the gatewayShipped for pool-backed SSH guests and host-console guests via owner-preserving SIGSTOP/SIGCONT; paused pool entries count as in-use capacity.

WebSocket surface

E2B uses WebSocket for the Sandbox.run_code streaming path. axum 0.7 with the ws feature handles this; CubeAPI implements it. The byte-level protocol (JSON framing, message types) mirrors E2B’s. Client code written against the E2B SDK does not notice the switch.

Authentication

From the quickstart:

export E2B_API_URL="http://127.0.0.1:3000"
export E2B_API_KEY="dummy"

In the open-source single-node install, E2B_API_KEY is ignored unless the operator configures COPPICE_API_KEYS or COPPICE_API_KEYS_FILE. When configured, the gateway requires X-API-Key or Authorization: Bearer ... on the API and envd surfaces while keeping /health public. See /appendix/api-key-auth.

What ports to FreeBSD

Axum and tokio are portable. CubeAPI does not touch Linux-specific primitives in its HTTP layer; the calls it makes downward go to CubeMaster over gRPC. So rather than cross-compile Tencent’s CubeAPI, we built our own Axum service — e2b-compat/ — that speaks the same E2B REST surface but dispatches downward into jails + bhyve rather than containerd + cube-hypervisor. On honor, the E2B Python SDK passes 10/10 of its API-server calls against this service, and the envd-compat endpoint on :49999 streams run_code NDJSON via jexec python3 — see /appendix/run-code-protocol for the wire format and the round-trip transcript.

The complexity in a FreeBSD port lives below the API layer, not at the API layer itself — as expected.

Caveats

”E2B” is a moving target

E2B’s own API has been shifting — endpoints added, versioned, deprecated. CubeAPI pins against a specific E2B version (implicitly, via the routes it chose to implement). If the E2B SDK introduces a new required endpoint, CubeAPI will either return 501 on that endpoint or lag behind until Cube adds it. “Drop-in” is pinned to a moment in time.

Template semantics

CUBE_TEMPLATE_ID in the quickstart is a Cube-specific ID that maps to a stored VM template. E2B’s template field in Sandbox.create is per-team on E2B cloud and maps to E2B-managed templates. Cube accepts E2B-shaped template IDs by treating them as opaque strings and looking them up in CubeMaster’s template registry. If you have existing “template” IDs on E2B cloud and expect them to work unchanged on Cube, they won’t — you upload templates to Cube separately.

Snapshots are not snapshots

E2B has two distinct concepts: /pause (stateful pause, live memory snapshot) and /snapshots (create a durable snapshot image, e.g., to fork from later). Cube implements the former and not the latter. If your code calls sandbox.snapshot() expecting to fork later, it’ll get a 501. If it calls sandbox.pause()/sandbox.resume(), it works.