REST API

REST API reference

The sandbox REST API lives at https://api.orkestr.eu/v1. Use it directly from any language, or pick the Python or JS SDK and skip the wire format entirely.

Conventions
All requests and responses use JSON with snake_case field names. Errors return a FastAPI-style {"detail": "..."} body plus an optional code field for machine-readable dispatch.

Base URL

bash
https://api.orkestr.eu/v1

Authentication

Every request must carry a Bearer token in the Authorization header. Mint tokens in the orkestr console with the sandboxes:read and sandboxes:write scopes. Tokens that lack the required scope get 403 Forbidden.

bash
curl https://api.orkestr.eu/v1/sandboxes \
  -H "Authorization: Bearer $ORKESTR_API_KEY"

Endpoints

Ten endpoints across lifecycle, exec, files, and pause / resume:

  • POST /v1/sandboxes - create a sandbox
  • GET /v1/sandboxes - list yours
  • GET /v1/sandboxes/{id} - get one
  • DELETE /v1/sandboxes/{id} - terminate
  • POST /v1/sandboxes/{id}/exec - run a command
  • POST /v1/sandboxes/{id}/files - write file
  • GET /v1/sandboxes/{id}/files?path=... - read file or list dir
  • DELETE /v1/sandboxes/{id}/files?path=... - delete file
  • POST /v1/sandboxes/{id}/pause - snapshot + suspend
  • POST /v1/sandboxes/{id}/resume - restore

Create a sandbox

POST /v1/sandboxes requires the sandboxes:write scope.

Request

import httpx, os

response = httpx.post(
    "https://api.orkestr.eu/v1/sandboxes",
    headers={"Authorization": f"Bearer {os.environ['ORKESTR_API_KEY']}"},
    json={
        "template": "python-3.12",
        "cpu": 1.0,
        "memory_mb": 2048,
        "network": "off",
        "timeout_seconds": 600,
        "env": {"OPENAI_API_KEY": "sk-..."},
        "metadata": {"agent_run": "r_42"},
    },
)
sandbox = response.json()
ParameterTypeDescription
templaterequiredstringOne of python-3.12, python-3.12-bare, node-22, ubuntu-24.04.
cpunumbervCPU allocation. Default 1.0. Plan-capped.
memory_mbintegerRAM in MB. Default 2048. Plan-capped.
networkstringoff, restricted, or open.
timeout_secondsintegerAuto-terminate after this many seconds (10 - 3600).
envobjectEnvironment variables exposed to processes.
metadataobjectCaller-defined tags echoed back on every response. Max 16 keys.
regionstringfsn1 or hel1. Omit for control-plane choice.

Response

json
{
  "id": "sbx_01HXYZ...",
  "status": "running",
  "template": "python-3.12",
  "network": "off",
  "cpu": 1.0,
  "memory_mb": 2048,
  "region": "fsn1",
  "created_at": "2026-05-19T14:22:11Z",
  "expires_at": "2026-05-19T14:32:11Z",
  "endpoints": {
    "exec": "/v1/sandboxes/sbx_01HXYZ.../exec",
    "files": "/v1/sandboxes/sbx_01HXYZ.../files",
    "pause": "/v1/sandboxes/sbx_01HXYZ.../pause",
    "resume": "/v1/sandboxes/sbx_01HXYZ.../resume"
  },
  "metadata": {"agent_run": "r_42"}
}

Run a command

POST /v1/sandboxes/{id}/exec requires sandboxes:write. Set stream to false for a buffered response, true for an SSE stream.

Request

httpx.post(
    "https://api.orkestr.eu/v1/sandboxes/sbx_01HXYZ.../exec",
    headers={"Authorization": f"Bearer {os.environ['ORKESTR_API_KEY']}"},
    json={
        "command": "python -c 'print(2+2)'",
        "cwd": "/workspace",
        "timeout_seconds": 60,
        "stream": False,
    },
).json()

Buffered response (stream=false)

json
{
  "stdout": "4\n",
  "stderr": "",
  "exit_code": 0,
  "duration_ms": 18
}

Streaming response (stream=true)

Content type is text/event-stream. Each frame is a data: line containing a JSON object with a kind field. data on chunk frames is base64-encoded.

bash
data: {"kind":"exec_started","pid":42}

data: {"kind":"exec_chunk","stream":"stdout","data":"NA=="}

data: {"kind":"exec_chunk","stream":"stderr","data":"d2Fybg=="}

data: {"kind":"exec_exited","code":0,"duration_ms":23}

Frame kinds:

ParameterTypeDescription
exec_startedeventSent once at the start. Carries the in-VM pid.
exec_chunkeventOutput burst. Has stream (stdout / stderr) and base64-encoded data.
exec_exitedeventFinal frame. Carries code and duration_ms.
erroreventError frame instead of exec_exited. Has code (e.g. exec_timeout, killed_by_signal) and message.

Files

Three endpoints sharing the path under /v1/sandboxes/{id}/files. All file content is base64 over the wire so binary round-trips cleanly. Reads and writes are capped at 16 MiB per request.

Write a file

POST /v1/sandboxes/{id}/files requires sandboxes:write. Writes go to /workspace or /tmp.

import base64
httpx.post(
    f"{BASE}/sandboxes/{sandbox_id}/files",
    headers={"Authorization": f"Bearer {token}"},
    json={
        "path": "/workspace/main.py",
        "content": base64.b64encode(b"print(2+1)").decode(),
        "mode": 0o644,
    },
)

Read a file or list a directory

GET /v1/sandboxes/{id}/files?path=... requires sandboxes:read. Returns one shape for both cases - is_dir tells you which fields are populated.

File response

json
{
  "path": "/workspace/main.py",
  "is_dir": false,
  "content": "cHJpbnQoMisxKQ==",
  "size": 10
}

Directory response

json
{
  "path": "/workspace",
  "is_dir": true,
  "entries": [
    {
      "name": "main.py",
      "is_dir": false,
      "size": 10,
      "mode": 420,
      "modified_at": "2026-05-19T14:23:00Z"
    }
  ]
}

Delete a file

DELETE /v1/sandboxes/{id}/files?path=... requires sandboxes:write. Returns 204 No Content on success. Directories are rejected; terminate the sandbox to drop everything.

Pause and resume

POST /v1/sandboxes/{id}/pause snapshots the sandbox and stops the compute meter. Returns the sandbox id and the server-side snapshot id; pass the sandbox id back to /resume when you want to come back.

Pause response

json
{
  "sandbox_id": "sbx_01HXYZ...",
  "snapshot_id": "snap_...",
  "status": "paused",
  "snapshot_size_mb": 1024,
  "created_at": "2026-05-19T14:30:00Z"
}

POST /v1/sandboxes/{id}/resume restores from the most recent snapshot for that sandbox. Returns the same shape as create. May be served from a different host than the original; restore latency depends on snapshot size.

Errors

Error bodies are JSON with at minimum a detail string. Many also carry a machine-readable code - dispatch on that when present, fall back to the HTTP status code otherwise.

json
{
  "detail": "API token missing required scope(s): sandboxes:write",
  "code": "missing_scope"
}
ParameterTypeDescription
401 UnauthorizedMissing, invalid, or expired API token.
403 ForbiddenToken valid but lacks the required scope.
404 Not FoundSandbox id doesn't exist or doesn't belong to the caller.
409 ConflictOperation invalid in current state. code: snapshot_cap_reached for the pause-over-cap case.
429 Too Many RequestsPlan rate limit hit. retry_after (seconds) included in body.
5xxServer error. Retries with backoff are appropriate; capture x-request-id from the response for support tickets.

Request IDs

Every response carries an x-request-id header. Include it in support tickets so we can correlate against server logs.