Reference

REST API

The control plane is a Fastify REST API. Everything else — the SDK, the MCP server, the console — is a client of these routes. All session routes are authenticated and org-scoped.

Base & auth#

The dev server listens on http://127.0.0.1:8088. Every session route requires a credential in the Authorization: Bearer … header — either a user JWT (HS256, verified locally against ENCLAVE_JWT_SECRET) or an API key of the form ek_<id>_<secret> (introspected against the console-api identity authority and cached briefly). The resolved principal carries an orgId, a role, and a set of scopes.

Scopes
Session routes are guarded by scope — reads (including the audit trail) require sessions:read; mutations require sessions:write. Roles map to scopes (owner > admin > developer > viewer); a viewer is rejected from session-create by the control plane itself, not just hidden in the UI.

Routes#

MethodPathScopePurpose
GET/healthznoneLiveness + which backend is wired and reachable. Unauthenticated.
POST/sessionssessions:writeCreate and run a session from a CreateSessionRequest. Returns the public Session.
GET/sessionssessions:readList sessions in the caller's org, newest first.
GET/sessions/:idsessions:readSession status — no token field, no brokered secret.
GET/sessions/:id/resultsessions:readThe structured SessionResult once terminal.
GET/sessions/:id/auditsessions:readThe immutable per-session audit trail.
GET/sessions/:id/streamsessions:readLive SSE stream: phase / stdout / stderr / audit / result / end.
DELETE/sessions/:idsessions:writeTear down the session and reclaim every per-session object.

Create a session#

The request body is a CreateSessionRequest — see data contracts for the full shape. Only code is required; the control plane fills defaults for language (python), egress (deny_all), and limits.

create-session.httphttp
POST /sessions
Authorization: Bearer ek_<id>_<secret>
Content-Type: application/json

{
  "code": "print('hi'); enclave.result({ 'ok': True })",
  "language": "python",
  "egress": { "mode": "deny_all", "allow": [] },
  "limits": { "memoryMiB": 256, "wallClockSeconds": 30 }
}

Streaming (SSE)#

GET /sessions/:id/stream returns a text/event-stream of StreamFrame objects. Each frame is a discriminated union on kind — a phase change, a stdout/stderr chunk, an audit event, the final result, or an end marker. The stream closes after the end frame.

stream.txttext
GET /sessions/:id/stream      (text/event-stream)

event: message
data: {"kind":"phase","phase":"running","ts":"…"}

data: {"kind":"stdout","chunk":"hi\n","ts":"…"}

data: {"kind":"audit","event":{"type":"egress_denied", …}}

data: {"kind":"result","result":{"exitCode":0, …},"ts":"…"}

data: {"kind":"end","phase":"succeeded","ts":"…"}

Tenancy & errors#

Every session belongs to exactly one org. Requests are scoped to the principal's orgId: a request for another org's session returns 404 — not 403 — so existence is never leaked across orgs. Common statuses:

FieldTypeDescription
401UnauthorizedMissing or invalid credential. There is no anonymous access path.
403ForbiddenAuthenticated, but the role lacks the required scope.
404Not FoundUnknown session or a session in another org — indistinguishable by design.
400Bad RequestMalformed CreateSessionRequest.