All protected endpoints require the Authorization: Bearer <token> header.
Base URL: http://localhost:9800
Public endpoint. No authentication required.
Response 200 OK
{
"status": "healthy",
"version": "0.1.0",
"docker_available": false,
"active_containers": 0,
"uptime_seconds": 42,
"components": {
"wasm_engine": { "status": "healthy", "detail": { "tools_loaded": 3 } },
"docker": { "status": "healthy", "detail": { "active_containers": 0 } },
"agents": { "status": "healthy", "detail": { "active_agents": 0 } }
}
}| Field | Type | Description |
|---|---|---|
status |
string | "healthy" or "degraded" (degraded when Docker unavailable) |
version |
string | Server version from Cargo.toml |
docker_available |
bool | Whether Docker daemon is reachable |
active_containers |
integer | Number of running containers |
uptime_seconds |
integer | Seconds since server start |
components |
object | Per-component health (wasm_engine, docker, agents) |
Public endpoint. Prometheus-format metrics.
Response 200 OK — text/plain Prometheus exposition format.
Execute a WASM tool in the sandbox.
Request
{
"tool": "echo",
"params": { "hello": "world" },
"capabilities": {
"network": { "allowlist": ["api.github.com"] },
"credentials": ["github-token"]
}
}| Field | Type | Required | Description |
|---|---|---|---|
tool |
string | yes | Tool name (matches .wasm filename without extension) |
params |
object | yes | JSON parameters passed to the tool via stdin |
capabilities |
object | no | Requested capabilities (filtered against tool manifest) |
Security: The
capabilities.network.allowlistfrom the request is ignored. The allowlist is always taken from the registered tool manifest. The requestcapabilities.credentialsare filtered to only those listed in the manifest.
Response 200 OK
{
"status": "ok",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"output": {
"tool": "echo",
"echo": { "hello": "world" }
},
"error": null,
"metadata": {
"execution_time_ms": 12,
"fuel_consumed": 45000,
"logs": [],
"sanitization": {
"issues_found": 0,
"actions_taken": []
}
}
}| Field | Type | Description |
|---|---|---|
status |
string | "ok" on success |
request_id |
string | UUID for this execution |
output |
object | string | Tool's stdout parsed as JSON (see note below) |
error |
string | null | Error message if execution failed |
metadata.execution_time_ms |
integer | Wall-clock execution time |
metadata.fuel_consumed |
integer | WASM fuel units consumed |
metadata.logs |
array | Log entries emitted by the tool via host_call |
metadata.sanitization.issues_found |
integer | Number of credential/injection patterns detected |
metadata.sanitization.actions_taken |
array | Redaction actions applied to output |
Non-JSON stdout: If your tool writes plain text instead of JSON to stdout, the
outputfield will be a JSON string containing the raw text (notnull). For example, if the tool printshello world, the response will contain"output": "hello world". Stdout is also truncated to ~1MB if it exceeds the maximum size.
| Code | HTTP Status | Description |
|---|---|---|
tool_not_found |
404 | WASM module not loaded for this tool name |
timeout |
408 | Execution exceeded time limit |
fuel_exhausted |
408 | WASM fuel limit reached |
execution_error |
500 | WASM execution failed or panicked |
internal_error |
500 | Proxy setup or other server error |
List registered tool manifests.
Response 200 OK
[
{
"tool": {
"name": "http_request",
"description": "Make HTTP requests",
"version": "0.1.0"
},
"network": {
"allowlist": ["api.github.com"],
"max_concurrent": 5
},
"credentials": {
"available": ["github-token"]
},
"resources": null
}
]Note: This returns registered manifests, not all loaded WASM files. Tools loaded from the
tool_dirare executable even without a manifest, but they will have no network access (deny-all) and no credential access.
Manifests may include optional input_schema and output_schema fields containing JSON Schema objects that describe the tool's expected input parameters and output format respectively.
Get a single tool's manifest by name.
Response 200 OK — Returns a single ToolManifest object (same shape as items in GET /tools).
Response 404 Not Found
{
"error": "tool 'echo' not found",
"code": "tool_not_found",
"details": null
}Register a tool manifest (defines network allowlist, credential access, etc.).
The tool name must match a loaded WASM file. Names must contain only alphanumeric characters, hyphens, and underscores (max 64 chars).
Request
{
"tool": {
"name": "http_request",
"description": "Make HTTP requests to allowed endpoints",
"version": "0.1.0"
},
"network": {
"allowlist": ["api.github.com", "httpbin.org"],
"max_concurrent": 5
},
"credentials": {
"available": ["github-token"]
}
}Response 201 Created (new) / 200 OK (updated)
{
"status": "registered",
"tool": "http_request"
}Hot-reload all WASM tool modules from the tool directory.
Response 200 OK
{
"reloaded": 3,
"tools": ["echo", "http_request", "summarize"]
}Spawn a sandboxed Docker container for long-running or sub-agent tasks.
Request
{
"task": "Summarize the latest commits",
"image": "ghcr.io/n0xmare/clawbox-agent:latest",
"policy": "container",
"capabilities": {
"network": {
"allowlist": ["api.github.com"]
},
"credentials": ["github-token"],
"resources": {
"timeout_ms": 120000,
"memory_mb": 512,
"cpu_shares": 1024,
"max_output_bytes": 262144
}
},
"env": { "TASK_MODE": "summary" }
}| Field | Type | Required | Description |
|---|---|---|---|
task |
string | yes | Task description for the container |
image |
string | no | Docker image (default: ghcr.io/n0xmare/clawbox-agent:latest) |
policy |
string | no | wasm_only, container, or container_direct (default: wasm_only) |
capabilities |
object | no | Network allowlist, credentials, and resource limits. Defaults to no network access and no credentials if omitted. |
env |
object | no | Environment variables passed to the container |
Security: The
capabilities.network.allowlistfrom the request is ignored. The server uses the[container_policy].network_allowlistfrom config. Credentials are filtered against[container_policy].allowed_credentials.
Note: The
policymust be in the[container_policy].allowed_policieslist (default:["wasm_only", "container"]). Requesting a disallowed policy returns403 Forbidden.
Response 201 Created
{
"container_id": "clawbox-550e8400-e29b-41d4-a716-446655440000",
"status": "running",
"policy": "container",
"created_at": "2026-02-19T21:00:00Z",
"task_summary": "Summarize the latest commits",
"proxy_socket": "/run/clawbox/proxy.sock",
"resource_usage": null
}| Field | Type | Description |
|---|---|---|
container_id |
string | Unique clawbox container identifier |
status |
string | Container lifecycle status (running, completed, failed, timed_out) |
policy |
string | Sandbox policy in effect |
created_at |
string | ISO 8601 timestamp |
task_summary |
string | Brief description of the container's task |
proxy_socket |
string | In-container Unix socket path for the proxy |
resource_usage |
object | null | Resource consumption snapshot (see below), null initially |
{
"memory_bytes": 52428800,
"cpu_percent": 12.5,
"network_requests": 7,
"duration_ms": 45000
}| Field | Type | Description |
|---|---|---|
memory_bytes |
integer | Current memory usage in bytes |
cpu_percent |
float | CPU utilization (0.0–100.0) |
network_requests |
integer | Total outbound network requests made |
duration_ms |
integer | Wall-clock time the container has been running |
Containers run with network_mode: "none" — they have no direct internet access. All outbound HTTP must go through the clawbox proxy via a Unix domain socket mounted into the container.
The host creates a per-container proxy socket at ~/.clawbox/proxies/{container_id}/proxy.sock and bind-mounts it into the container at /run/clawbox/proxy.sock.
The container receives these environment variables automatically:
| Variable | Value | Description |
|---|---|---|
CLAWBOX_PROXY_SOCKET |
/run/clawbox/proxy.sock |
Unix socket path for the proxy |
CLAWBOX_PROXY_TOKEN |
{container_token} |
Bearer token for proxy authentication |
CLAWBOX_CONTAINER_ID |
{clawbox_id} |
This container's clawbox ID |
Reserved env vars: The following variables cannot be overridden via the
envfield — they are either set by clawbox or filtered out:
| Variable | Behavior |
|---|---|
CLAWBOX_PROXY_SOCKET |
Set by clawbox (always /run/clawbox/proxy.sock) |
CLAWBOX_PROXY_TOKEN |
Set by clawbox (unique per container) |
CLAWBOX_CONTAINER_ID |
Set by clawbox (container ID) |
HTTP_PROXY |
Filtered out (blocked) |
HTTPS_PROXY |
Filtered out (blocked) |
http_proxy |
Filtered out (blocked) |
https_proxy |
Filtered out (blocked) |
To make HTTP requests from inside a container, use the Unix socket directly:
# Shell — use curl with --unix-socket
curl --unix-socket /run/clawbox/proxy.sock \
-H "Authorization: Bearer $CLAWBOX_PROXY_TOKEN" \
http://localhost/https://api.github.com/repos/owner/repo# Python — use httpx with Unix domain socket transport
import httpx
import os
transport = httpx.HTTPTransport(uds="/run/clawbox/proxy.sock")
client = httpx.Client(transport=transport)
response = client.get(
"http://localhost/https://api.github.com/repos/owner/repo",
headers={"Authorization": f"Bearer {os.environ['CLAWBOX_PROXY_TOKEN']}"}
)
print(response.json())The proxy enforces the server-side network allowlist and injects credentials automatically. Requests to non-allowlisted hosts are blocked.
| Code | HTTP Status | Description |
|---|---|---|
policy_denied |
403 | Requested sandbox policy not in allowed_policies |
resource_exhausted |
429 | Container limit reached |
proxy_error |
500 | Failed to spawn per-container proxy |
container_error |
500 | Failed to create/start Docker container |
List all active containers.
Response 200 OK
[
{
"container_id": "clawbox-550e8400-...",
"status": "running",
"policy": "container",
"created_at": "2026-02-19T21:00:00Z",
"task_summary": "Summarize the latest commits",
"proxy_socket": "/run/clawbox/proxy.sock",
"resource_usage": null
}
]Get details for a specific container.
Response 200 OK — Returns a ContainerInfo object (same shape as items in GET /containers).
Response 404 Not Found
{
"error": "Container not found",
"code": "not_found",
"details": null
}Kill a running container, shut down its proxy, and collect output.
Response 200 OK
{
"container_id": "clawbox-550e8400-...",
"status": "killed",
"output": "..."
}Register a new agent.
Request
{
"agent_id": "my-agent",
"name": "My Agent",
"policy": "container",
"image": "ghcr.io/n0xmare/clawbox-agent:latest",
"capabilities": {},
"env": {},
"lifecycle": {
"max_idle_ms": 300000,
"max_lifetime_ms": 3600000
}
}Response 201 Created — Returns AgentInfo.
List all registered agents.
Response 200 OK — Returns AgentInfo[].
Get info about a specific agent.
Response 200 OK — Returns AgentInfo.
Response 404 Not Found
Start an agent's container.
Response 200 OK — Returns AgentInfo with status "Running".
Stop an agent's container.
Response 200 OK — Returns AgentInfo with status "Idle".
Remove an agent entirely.
Response 204 No Content
Response 404 Not Found
clawbox supports three execution policies that control how tools run:
| Policy | Isolation | Network | Use Case |
|---|---|---|---|
wasm_only |
WASM sandbox | Via host_call FFI → proxy |
Most restrictive. Recommended for untrusted tools. |
container |
Docker container | Via Unix socket proxy (network_mode=none) |
Container isolation + proxy security. For tools needing a full OS environment. |
container_direct |
Docker container | Direct (no proxy) | Least restrictive. Requires explicit opt-in in server config. Only for trusted tools needing raw socket access. |
wasm_only — Tool runs in a WASM sandbox with fuel and time limits. Network requests go through the host_call FFI, which routes them through the proxy pipeline (allowlist → credential injection → leak detection). The tool never sees real credentials or makes direct network calls.
container — Tool runs inside a Docker container with network_mode: "none". All HTTP traffic is routed through the clawbox proxy via a Unix domain socket (CLAWBOX_PROXY_SOCKET=/run/clawbox/proxy.sock). The proxy enforces the same allowlist and credential injection as WASM mode.
container_direct — Tool runs in a Docker container with direct network access (no proxy interception). This bypasses allowlist enforcement and credential injection. Requires explicit opt-in by adding "container_direct" to [container_policy].allowed_policies in server config. Use only for trusted tools that need raw socket access (e.g., SSH, WebSocket, or non-HTTP protocols).
| Field | Type | Default | Env Override | Description |
|---|---|---|---|---|
host |
string | "127.0.0.1" |
— | Bind address |
port |
integer | 9800 |
CLAWBOX_PORT |
HTTP API port |
auth_token |
string | (generated) | CLAWBOX_AUTH_TOKEN |
Bearer token for API auth |
unix_socket |
string | (disabled) | — | Unix socket path for same-machine clients |
max_concurrent_executions |
integer | 10 |
— | Maximum concurrent WASM executions (tower concurrency limit) |
| Field | Type | Default | Description |
|---|---|---|---|
tool_dir |
string | "./tools/wasm" |
Directory containing .wasm tool files |
default_fuel |
integer | 100000000 |
Default fuel limit for WASM execution |
default_timeout_ms |
integer | 30000 |
Default timeout for WASM execution |
watch_tools |
bool | true |
Watch tool_dir for changes and hot-reload WASM modules |
| Field | Type | Default | Description |
|---|---|---|---|
max_response_bytes |
integer | 1048576 |
Maximum response body size from proxied requests |
default_timeout_ms |
integer | 30000 |
Default timeout for proxied HTTP requests |
| Field | Type | Default | Description |
|---|---|---|---|
max_containers |
integer | 10 |
Maximum number of concurrent containers |
workspace_root |
string | "~/.clawbox/workspaces" |
Root directory for per-agent workspace mounts |
| Field | Type | Default | Description |
|---|---|---|---|
network_allowlist |
list | [] |
Server-side network allowlist applied to all containers |
allowed_credentials |
list | [] |
Credential names containers are allowed to request |
allowed_policies |
list | ["wasm_only", "container"] |
Which sandbox policies are permitted |
| Field | Type | Default | Description |
|---|---|---|---|
store_path |
string | "~/.clawbox/credentials.enc" |
Path to the encrypted credential store |
| Field | Type | Default | Description |
|---|---|---|---|
format |
string | "json" |
Log output format ("json" or "text") |
level |
string | "info" |
Log level (trace, debug, info, warn, error) |
audit_dir |
string | "./audit" |
Directory for audit log files |
| Limit | Default | Configurable Via |
|---|---|---|
| WASM fuel limit | 100,000,000 | [sandbox].default_fuel |
| WASM execution timeout | 30s | [sandbox].default_timeout_ms |
| Container proxy rate limit | 100 req/s per tool | [proxy].rate_limit |
| Request body size | 10 MB max | — |
| Max concurrent WASM executions | 10 | [server].max_concurrent_executions |
| Max containers | 10 | [containers].max_containers |
| Proxy response size | 1 MB | [proxy].max_response_bytes |
| Container idle timeout | Configurable per agent | lifecycle.max_idle_ms in agent config |
| HTTP redirect following | Disabled | — (SSRF protection) |
All endpoints except GET /health and GET /metrics require:
Authorization: Bearer <token>
The clawbox init command generates a random 64-character hex token and writes it to clawbox.toml. There is no default token — each installation gets a unique one.
Requests without a valid token receive 401 Unauthorized:
{
"error": "unauthorized",
"code": "auth_required",
"details": null
}All errors follow the ApiError shape:
{
"error": "human-readable error message",
"code": "machine_readable_code",
"details": null
}| Code | HTTP Status | Description |
|---|---|---|
auth_required |
401 | Missing or invalid bearer token |
tool_not_found |
404 | WASM module not loaded for this tool name |
not_found |
404 | Container or agent not found |
invalid_request |
400 | Invalid agent config or state transition |
policy_denied |
403 | Requested sandbox policy not allowed |
timeout |
408 | Execution exceeded time limit |
fuel_exhausted |
408 | WASM fuel limit reached |
execution_error |
500 | WASM execution failed |
internal_error |
500 | Server-side error (proxy setup, panic, etc.) |
proxy_error |
500 | Failed to spawn container proxy |
container_error |
500 | Failed to spawn Docker container |
resource_exhausted |
429/503 | Container limit or proxy port exhaustion |
For same-machine clients, clawbox can listen on a Unix domain socket for lower latency:
[server]
unix_socket = "/run/clawbox.sock"Connect via:
curl --unix-socket /run/clawbox.sock http://localhost/health