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.
snake_case field names. Errors return a FastAPI-style {"detail": "..."} body plus an optional code field for machine-readable dispatch.Base URL
https://api.orkestr.eu/v1Authentication
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.
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 sandboxGET /v1/sandboxes- list yoursGET /v1/sandboxes/{id}- get oneDELETE /v1/sandboxes/{id}- terminatePOST /v1/sandboxes/{id}/exec- run a commandPOST /v1/sandboxes/{id}/files- write fileGET /v1/sandboxes/{id}/files?path=...- read file or list dirDELETE /v1/sandboxes/{id}/files?path=...- delete filePOST /v1/sandboxes/{id}/pause- snapshot + suspendPOST /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()| Parameter | Type | Description |
|---|---|---|
| templaterequired | string | One of python-3.12, python-3.12-bare, node-22, ubuntu-24.04. |
| cpu | number | vCPU allocation. Default 1.0. Plan-capped. |
| memory_mb | integer | RAM in MB. Default 2048. Plan-capped. |
| network | string | off, restricted, or open. |
| timeout_seconds | integer | Auto-terminate after this many seconds (10 - 3600). |
| env | object | Environment variables exposed to processes. |
| metadata | object | Caller-defined tags echoed back on every response. Max 16 keys. |
| region | string | fsn1 or hel1. Omit for control-plane choice. |
Response
{
"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)
{
"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.
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:
| Parameter | Type | Description |
|---|---|---|
| exec_started | event | Sent once at the start. Carries the in-VM pid. |
| exec_chunk | event | Output burst. Has stream (stdout / stderr) and base64-encoded data. |
| exec_exited | event | Final frame. Carries code and duration_ms. |
| error | event | Error 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
{
"path": "/workspace/main.py",
"is_dir": false,
"content": "cHJpbnQoMisxKQ==",
"size": 10
}Directory response
{
"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
{
"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.
{
"detail": "API token missing required scope(s): sandboxes:write",
"code": "missing_scope"
}| Parameter | Type | Description |
|---|---|---|
| 401 Unauthorized | Missing, invalid, or expired API token. | |
| 403 Forbidden | Token valid but lacks the required scope. | |
| 404 Not Found | Sandbox id doesn't exist or doesn't belong to the caller. | |
| 409 Conflict | Operation invalid in current state. code: snapshot_cap_reached for the pause-over-cap case. | |
| 429 Too Many Requests | Plan rate limit hit. retry_after (seconds) included in body. | |
| 5xx | Server 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.