API Reference
Complete HTTP API documentation for Sandkasten.
Note
./bin/sandkasten ps. Run the daemon in the background with ./bin/sandkasten daemon -d; stop it with sudo ./bin/sandkasten stop. Validate security with ./bin/sandkasten security --config sandkasten.yaml.Authentication
All API endpoints (except /healthz and web UI) require authentication:
Authorization: Bearer <api_key>
Base URL
Default: http://localhost:8080
Sessions
Create Session
POST /v1/sessions
Request:
{
"image": "python",
"ttl_seconds": 3600,
"workspace_id": "user123-project"
}
Response:
{
"id": "abc123def456",
"image": "python",
"status": "running",
"cwd": "/workspace",
"workspace_id": "user123-project",
"created_at": "2026-02-08T10:00:00Z",
"expires_at": "2026-02-08T11:00:00Z"
}
Tip
pool.enabled is true in config, sessions (with or without workspace_id) may be served from a pre-warmed pool in ~50–80ms instead of ~200–450ms cold create. For workspace_id, the workspace is bind-mounted at acquire time. See Session Pool.Get Session
GET /v1/sessions/{id}
Response: Same as create session
List Sessions
GET /v1/sessions
Response:
[
{
"id": "abc123",
"image": "python",
"status": "running",
...
}
]
Destroy Session
DELETE /v1/sessions/{id}
Response:
{"ok": true}
Session Stats
GET /v1/sessions/{id}/stats
Response:
{
"memory_bytes": 1239040,
"memory_limit": 536870912,
"cpu_usage_usec": 10442
}
Execution
Execute Command (Blocking)
POST /v1/sessions/{id}/exec
Request:
{
"cmd": "python3 -c 'print(42)'",
"timeout_ms": 30000,
"raw_output": false
}
Response:
{
"exit_code": 0,
"cwd": "/workspace",
"output": "42\n",
"truncated": false,
"duration_ms": 42
}
Notes:
- Shell is persistent (cd, env vars, background processes persist)
- Output is combined stdout+stderr
- Output is cleaned by default (no echoed command/prompt noise, normalized newlines, ANSI stripped)
- Set
raw_output: trueto get raw PTY output for debugging - Truncated after 1MB
- Returns when command completes
- Large commands are supported: commands over 16 KiB are staged as a temporary script in
/workspace/.sandkasten/and then executed via a short command - Maximum
cmdsize is 1 MiB; larger payloads return400 INVALID_REQUESTwith guidance to use/fs/write
Execute Command (Streaming)
POST /v1/sessions/{id}/exec/stream
Request: Same as blocking exec (raw_output also supported)
Response: Server-Sent Events (SSE)
event: chunk
data: {"chunk":"Hello\n","timestamp":1707390000000}
event: chunk
data: {"chunk":"World\n","timestamp":1707390001000}
event: done
data: {"exit_code":0,"cwd":"/workspace","duration_ms":1234}
Events:
chunk- Output chunk with timestampdone- Command completederror- Error occurred
Notes:
- Real-time output for long commands
- Same persistent shell semantics
- Client must support SSE
- Large commands are supported with the same staging behavior as blocking exec (inline threshold 16 KiB, API limit 1 MiB)
See Streaming Guide for details.
Filesystem
Write File
POST /v1/sessions/{id}/fs/write
Request:
{
"path": "/workspace/hello.py",
"content_base64": "cHJpbnQoJ2hlbGxvJyk="
}
Or with text:
{
"path": "/workspace/hello.py",
"text": "print('hello')"
}
Response:
{"ok": true}
Upload File (multipart)
POST /v1/sessions/{id}/fs/upload
Content-Type: multipart/form-data
Upload one or more files via multipart/form-data. Ideal for binary files, drag-and-drop, or HTML file inputs.
Form fields:
fileorfiles(required) - One or more file partspath(optional) - Target directory under/workspace, defaults to/workspace
Files are saved as {path}/{filename}. Max 10 MB per request.
Example (curl):
curl -X POST http://localhost:8080/v1/sessions/$SESSION_ID/fs/upload \
-H "Authorization: Bearer sk-..." \
-F "file=@./myfile.py" \
-F "path=/workspace"
Response:
{"ok": true, "paths": ["/workspace/myfile.py"]}
Read File
GET /v1/sessions/{id}/fs/read?path=/workspace/hello.py
Query Parameters:
path(required) - File pathmax_bytes(optional) - Max bytes to read
Response:
{
"path": "/workspace/hello.py",
"content_base64": "cHJpbnQoJ2hlbGxvJyk=",
"truncated": false
}
Workspaces
List Workspaces
GET /v1/workspaces
Response:
{
"workspaces": [
{
"id": "user123-project",
"created_at": "2026-02-08T10:00:00Z",
"labels": {
"sandkasten.workspace_id": "user123-project"
}
}
]
}
Write Workspace File
POST /v1/workspaces/{id}/fs/write
Write a file directly to a workspace (no session required). Workspace is created if it does not exist.
Request:
{
"path": "code.py",
"text": "print('hello')"
}
Or with base64:
{
"path": "data.bin",
"content_base64": "aGVsbG8="
}
Response:
{"ok": true}
Upload Workspace File (multipart)
POST /v1/workspaces/{id}/fs/upload
Content-Type: multipart/form-data
Upload one or more files directly to a workspace (no session required). Workspace is created if it does not exist.
Form fields:
fileorfiles(required) - One or more file partspath(optional) - Target directory within workspace root, e.g.subdirforsubdir/filename
Example:
curl -X POST http://localhost:8080/v1/workspaces/my-project/fs/upload \
-H "Authorization: Bearer sk-..." \
-F "file=@./data.csv"
Response:
{"ok": true, "paths": ["data.csv"]}
Delete Workspace
DELETE /v1/workspaces/{id}
Response:
{"ok": true}
Note: Destroys all data in the workspace permanently.
Health Check
Health Check
GET /healthz
Response:
{"status": "ok"}
Note: No authentication required.
Status Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created (session) |
| 400 | Bad request (invalid JSON, missing params) |
| 401 | Unauthorized (invalid API key) |
| 404 | Not found (session doesn’t exist) |
| 500 | Internal server error |
Error Format
{
"error": "session not found: abc123"
}
Rate Limits
No rate limits by default. Implement in reverse proxy if needed.
Examples
cURL
API_KEY="sk-sandbox-quickstart"
BASE_URL="http://localhost:8080"
# Create session
SESSION=$(curl -s -X POST $BASE_URL/v1/sessions \
-H "Authorization: Bearer $API_KEY" \
-d '{"image":"python"}' | jq -r .id)
# Execute
curl -X POST $BASE_URL/v1/sessions/$SESSION/exec \
-H "Authorization: Bearer $API_KEY" \
-d '{"cmd":"echo hello"}'
# Write file
echo -n "print('hello')" | base64 | \
jq -R '{path:"/workspace/test.py",content_base64:.}' | \
curl -X POST $BASE_URL/v1/sessions/$SESSION/fs/write \
-H "Authorization: Bearer $API_KEY" \
-d @-
# Read file
curl "$BASE_URL/v1/sessions/$SESSION/fs/read?path=/workspace/test.py" \
-H "Authorization: Bearer $API_KEY" | \
jq -r .content_base64 | base64 -d
# Destroy
curl -X DELETE $BASE_URL/v1/sessions/$SESSION \
-H "Authorization: Bearer $API_KEY"