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.
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#
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /healthz | none | Liveness + which backend is wired and reachable. Unauthenticated. |
| POST | /sessions | sessions:write | Create and run a session from a CreateSessionRequest. Returns the public Session. |
| GET | /sessions | sessions:read | List sessions in the caller's org, newest first. |
| GET | /sessions/:id | sessions:read | Session status — no token field, no brokered secret. |
| GET | /sessions/:id/result | sessions:read | The structured SessionResult once terminal. |
| GET | /sessions/:id/audit | sessions:read | The immutable per-session audit trail. |
| GET | /sessions/:id/stream | sessions:read | Live SSE stream: phase / stdout / stderr / audit / result / end. |
| DELETE | /sessions/:id | sessions:write | Tear 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.
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.
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:
| Field | Type | Description |
|---|---|---|
| 401 | Unauthorized | Missing or invalid credential. There is no anonymous access path. |
| 403 | Forbidden | Authenticated, but the role lacks the required scope. |
| 404 | Not Found | Unknown session or a session in another org — indistinguishable by design. |
| 400 | Bad Request | Malformed CreateSessionRequest. |