Skip to content

HTTP API

agentkernel includes a REST API for programmatic sandbox management.

Starting the Server

# As a background service (recommended — survives reboots)
brew services start thrashr888/agentkernel/agentkernel

# Or start manually on default port (18888)
agentkernel serve

# Custom port
agentkernel serve --port 3000

# With API key authentication
agentkernel serve --api-key your-secret

# Multiple API keys
agentkernel serve --api-key key1 --api-key key2

# Load keys from a file (one per line, # comments supported)
agentkernel serve --api-key-file /path/to/keys.txt

# Or via environment variable
AGENTKERNEL_API_KEY=your-secret agentkernel serve

# With OpenTelemetry trace export
agentkernel serve --otel-endpoint http://localhost:4318

# With webhook notifications
agentkernel serve --webhook-url http://localhost:9999/hooks

# Multiple webhooks + OTel
agentkernel serve --otel-endpoint http://localhost:4318 \
  --webhook-url http://hook1.example.com \
  --webhook-url http://hook2.example.com

Authentication

When API key authentication is enabled (via --api-key, --api-key-file, AGENTKERNEL_API_KEY, or [api].api_key in config), all requests require an Authorization header, except for /health and /status:

Authorization: Bearer your-secret
curl -H "Authorization: Bearer your-secret" http://localhost:18888/sandboxes

Multiple keys can be configured for key rotation or multi-tenant setups. Any valid key will authenticate the request.

Endpoints

Health Check

GET /health
curl http://localhost:18888/health
{"status": "ok"}

Server Status

GET /status
curl http://localhost:18888/status
{
  "success": true,
  "data": {"version": "0.15.0", "backend": "docker", "api_key_configured": false}
}

Server Statistics

GET /stats
curl http://localhost:18888/stats
{
  "success": true,
  "data": {
    "sandbox_count": 12,
    "sandbox_limit": 0,
    "backend": "docker",
    "uptime_seconds": 3600,
    "version": "0.15.0",
    "resource_usage": {
      "cpu_percent": 65.2,
      "memory_used_mb": 8192,
      "memory_total_mb": 16384,
      "disk_used_mb": 4096
    }
  }
}

The resource_usage field provides host-level CPU, memory, and disk metrics for fleet load-balancing.

Event Stream (SSE)

GET /events

Streams sandbox lifecycle events via Server-Sent Events. Requires authentication when API keys are configured. Requires --webhook-url or --otel-endpoint to enable the event bus.

# Stream all events
curl -N http://localhost:18888/events

# Filter to a specific sandbox
curl -N http://localhost:18888/events?sandbox=my-sandbox

Events: - sandbox.created — sandbox was created and started - sandbox.exec.completed — command execution finished (includes exit_code, duration_ms) - sandbox.deleted — sandbox was removed

Event payload:

{
  "event": "sandbox.exec.completed",
  "timestamp": "2026-02-23T12:00:00Z",
  "sandbox": "my-sandbox",
  "labels": {},
  "metadata": {
    "command": "echo hello",
    "duration_ms": 42,
    "success": true,
    "exit_code": 0
  }
}

Observability Flags

Flag Description
--otel-endpoint URL OTLP/HTTP endpoint for trace export (e.g. http://localhost:4318)
--webhook-url URL POST events to this URL (can be repeated)

When --otel-endpoint is set, every HTTP request creates an OTel span with W3C traceparent propagation. Pass a traceparent header on incoming requests to link sandbox operations to your existing traces.

When executing commands (POST /sandboxes/{name}/exec), the TRACEPARENT and TRACESTATE environment variables are automatically injected into the sandbox, enabling code inside the sandbox to continue the distributed trace.

Garbage Collection

POST /gc
curl -X POST http://localhost:18888/gc
{
  "success": true,
  "data": {"removed": ["expired-sandbox-1", "old-test"]}
}

Removes sandboxes that have exceeded their time-to-live.

Run Command

Execute a command in a temporary sandbox.

POST /run
curl -X POST http://localhost:18888/run \
  -H "Content-Type: application/json" \
  -d '{"command": ["python3", "-c", "print(1+1)"]}'
{
  "success": true,
  "data": {"output": "2\n"}
}

Request body:

Field Type Required Description
command array Yes Command and arguments
image string No Docker image (auto-detected if omitted)
profile string No Security profile
fast bool No Use container pool (default: true)

Run Command (Streaming)

Execute a command with Server-Sent Events (SSE) streaming.

POST /run/stream
curl -X POST http://localhost:18888/run/stream \
  -H "Content-Type: application/json" \
  -d '{"command": ["python3", "long_script.py"]}'

Response (SSE stream):

event: started
data: {"sandbox":"sandbox-abc123"}

event: progress
data: {"stage":"creating"}

event: progress
data: {"stage":"starting"}

event: progress
data: {"stage":"executing"}

event: output
data: {"content":"Processing step 1...\n"}

event: output
data: {"content":"Processing step 2...\n"}

event: done
data: {"exit_code":0}

Event types:

Event Data Description
started {"sandbox": "name"} Command execution started
progress {"stage": "..."} Execution stage (creating, starting, executing)
output {"content": "..."} Command output (stdout/stderr)
done {"exit_code": 0} Command completed successfully
error {"message": "..."} Error occurred

Request body: Same as /run

Use cases: - Long-running commands - Real-time output display - Progress tracking

List Sandboxes

GET /sandboxes
GET /sandboxes?label=key:value
# List all sandboxes
curl http://localhost:18888/sandboxes

# Filter by labels (multiple labels are ANDed)
curl "http://localhost:18888/sandboxes?label=env:prod&label=team:ml"
{
  "success": true,
  "data": [
    {"name": "my-sandbox", "uuid": "019abc12-1234-7def-89ab-0123456789ab", "status": "running", "backend": "docker", "ip": "172.17.0.3"},
    {"name": "test", "uuid": "019abc12-2345-7def-89ab-0123456789ab", "status": "stopped", "backend": "docker"}
  ]
}

The ip field contains the container's Docker bridge network IP address. It is only present for running sandboxes.

Create Sandbox

POST /sandboxes
curl -X POST http://localhost:18888/sandboxes \
  -H "Content-Type: application/json" \
  -d '{"name": "my-sandbox", "image": "python:3.12-alpine"}'
{
  "success": true,
  "data": {"name": "my-sandbox", "uuid": "019abc12-1234-7def-89ab-0123456789ab", "status": "running", "backend": "docker"}
}

Request body:

Field Type Required Description
name string Yes Sandbox name
image string No Docker image (auto-detected if omitted)
vcpus integer No Number of vCPUs (default: 1)
memory_mb integer No Memory in MB (default: 512)
profile string No Security profile: permissive, moderate, restrictive
labels object No Key-value labels for fleet management and filtering
description string No Human-readable description
lifecycle object No Lifecycle policy (auto_stop_after_seconds, auto_archive_after_seconds, auto_delete_after_seconds)

With labels and description:

curl -X POST http://localhost:18888/sandboxes \
  -H "Content-Type: application/json" \
  -d '{
    "name": "eval-sandbox",
    "image": "python:3.12-alpine",
    "labels": {"scenario": "drift_s3", "model": "sonnet", "eval_run": "pr-123"},
    "description": "Drift scenario evaluation"
  }'

Update Sandbox

PATCH /sandboxes/{name}
curl -X PATCH http://localhost:18888/sandboxes/my-sandbox \
  -H "Content-Type: application/json" \
  -d '{"labels": {"env": "staging"}, "description": "Updated description"}'
{
  "success": true,
  "data": {"name": "my-sandbox", "uuid": "019abc12-...", "status": "running", "backend": "docker"}
}

Request body:

Field Type Required Description
labels object No Replace all labels
description string No Update description
lifecycle object or null No Set lifecycle policy or clear it with null

Get Sandbox

GET /sandboxes/{name}
curl http://localhost:18888/sandboxes/my-sandbox
{
  "success": true,
  "data": {
    "name": "my-sandbox",
    "uuid": "019abc12-1234-7def-89ab-0123456789ab",
    "status": "running",
    "backend": "docker",
    "ip": "172.17.0.3",
    "image": "python:3.12-alpine",
    "vcpus": 1,
    "memory_mb": 512,
    "created_at": "2026-01-30T12:00:00Z"
  }
}

The response includes resource limits and metadata when available. The ip field is only present for running sandboxes. Fields that are unknown are omitted.

Get Sandbox by UUID

GET /sandboxes/by-uuid/{uuid}
curl http://localhost:18888/sandboxes/by-uuid/019abc12-1234-7def-89ab-0123456789ab

Execute in Sandbox

POST /sandboxes/{name}/exec
curl -X POST http://localhost:18888/sandboxes/my-sandbox/exec \
  -H "Content-Type: application/json" \
  -d '{"command": ["ls", "-la"]}'
{
  "success": true,
  "data": {"output": "total 0\ndrwxr-xr-x..."}
}

Stop Sandbox

POST /sandboxes/{name}/stop
curl -X POST http://localhost:18888/sandboxes/my-sandbox/stop

Delete Sandbox

DELETE /sandboxes/{name}
curl -X DELETE http://localhost:18888/sandboxes/my-sandbox

Extend TTL

Extend a sandbox's time-to-live.

POST /sandboxes/{name}/extend
curl -X POST http://localhost:18888/sandboxes/my-sandbox/extend \
  -H "Content-Type: application/json" \
  -d '{"by": "1h"}'
{
  "success": true,
  "data": {"expires_at": "2026-02-05T15:00:00Z"}
}

Request body:

Field Type Required Description
by string No Duration to extend (default: "1h"). Examples: "30m", "2h", "1d"

Recover Archived Sandbox

Clears archive metadata so an archived sandbox can be started again.

POST /sandboxes/{name}/recover
curl -X POST http://localhost:18888/sandboxes/my-sandbox/recover

Reconcile Lifecycle Policies

Applies lifecycle policies across all sandboxes (or previews actions).

POST /lifecycle/reconcile
# Apply lifecycle actions
curl -X POST http://localhost:18888/lifecycle/reconcile

# Dry run (preview only)
curl -X POST http://localhost:18888/lifecycle/reconcile \
  -H "Content-Type: application/json" \
  -d '{"dry_run": true}'

File Operations

Read, write, and delete files inside a running sandbox.

Write File

PUT /sandboxes/{name}/files/{path...}
curl -X PUT http://localhost:18888/sandboxes/my-sandbox/files/tmp/hello.txt \
  -H "Content-Type: application/json" \
  -d '{"content": "hello world"}'
{
  "success": true,
  "data": "Wrote 11 bytes to /tmp/hello.txt"
}

Request body:

Field Type Required Description
content string Yes File content (text or base64-encoded)
encoding string No utf8 (default) or base64

Binary file (base64):

curl -X PUT http://localhost:18888/sandboxes/my-sandbox/files/tmp/data.bin \
  -H "Content-Type: application/json" \
  -d '{"content": "aGVsbG8=", "encoding": "base64"}'

Read File

GET /sandboxes/{name}/files/{path...}
curl http://localhost:18888/sandboxes/my-sandbox/files/tmp/hello.txt
{
  "success": true,
  "data": {
    "content": "hello world",
    "encoding": "utf8",
    "size": 11
  }
}

Binary files are returned as base64 with "encoding": "base64".

Delete File

DELETE /sandboxes/{name}/files/{path...}
curl -X DELETE http://localhost:18888/sandboxes/my-sandbox/files/tmp/hello.txt
{
  "success": true,
  "data": "Deleted /tmp/hello.txt"
}

Sandbox Logs

Retrieve audit log entries for a specific sandbox.

GET /sandboxes/{name}/logs
curl http://localhost:18888/sandboxes/my-sandbox/logs
{
  "success": true,
  "data": [
    {
      "timestamp": "2026-01-30T12:00:00Z",
      "event": "sandbox_created",
      "sandbox": "my-sandbox"
    }
  ]
}

Returns all audit events associated with the sandbox, sorted by timestamp. See audit logging for event types.

Batch Execution

Run multiple commands in parallel, each in its own temporary sandbox.

POST /batch/run
curl -X POST http://localhost:18888/batch/run \
  -H "Content-Type: application/json" \
  -d '{
    "commands": [
      {"command": ["echo", "hello"]},
      {"command": ["python3", "-c", "print(2+2)"]}
    ]
  }'
{
  "success": true,
  "data": {
    "results": [
      {"output": "hello\n", "error": null},
      {"output": "4\n", "error": null}
    ]
  }
}

Request body:

Field Type Required Description
commands array Yes List of commands to run
commands[].command array Yes Command and arguments

Each command runs in an isolated container from the pool. Results are returned in the same order as the input commands.

Snapshots

List Snapshots

GET /snapshots
curl http://localhost:18888/snapshots
{
  "success": true,
  "data": [
    {
      "name": "checkpoint-1",
      "sandbox": "my-sandbox",
      "image_tag": "agentkernel-snap:checkpoint-1",
      "backend": "docker",
      "base_image": "python:3.12-alpine",
      "vcpus": 2,
      "memory_mb": 512,
      "created_at": "2026-02-05T12:00:00Z"
    }
  ]
}

Take Snapshot

POST /snapshots
curl -X POST http://localhost:18888/snapshots \
  -H "Content-Type: application/json" \
  -d '{"sandbox": "my-sandbox", "name": "checkpoint-1"}'
{
  "success": true,
  "data": {
    "name": "checkpoint-1",
    "sandbox": "my-sandbox",
    "image_tag": "agentkernel-snap:checkpoint-1",
    "backend": "docker",
    "base_image": "python:3.12-alpine",
    "vcpus": 2,
    "memory_mb": 512,
    "created_at": "2026-02-05T12:00:00Z"
  }
}

Request body:

Field Type Required Description
sandbox string Yes Name of the sandbox to snapshot
name string Yes Name for the snapshot

Get Snapshot

GET /snapshots/{name}
curl http://localhost:18888/snapshots/checkpoint-1

Returns snapshot details (same format as list).

Delete Snapshot

DELETE /snapshots/{name}
curl -X DELETE http://localhost:18888/snapshots/checkpoint-1
{
  "success": true,
  "data": "Snapshot deleted"
}

Restore Snapshot

POST /snapshots/{name}/restore
curl -X POST http://localhost:18888/snapshots/checkpoint-1/restore \
  -H "Content-Type: application/json" \
  -d '{"as_name": "restored-sandbox"}'
{
  "success": true,
  "data": {
    "sandbox": "restored-sandbox",
    "from_snapshot": "checkpoint-1"
  }
}

Request body:

Field Type Required Description
as_name string No Name for the restored sandbox (defaults to {original}-restored)

Browser Automation

Control a persistent headless browser inside a sandbox via ARIA snapshots.

The browser server starts automatically on first use. It runs Chromium via Playwright inside the sandbox and exposes an internal HTTP API on port 9222.

Start Browser Server

POST /sandboxes/{name}/browser/start
curl -X POST http://localhost:18888/sandboxes/my-browser/browser/start

Starts the in-sandbox browser server. Called automatically by other browser endpoints if needed.

List Pages

GET /sandboxes/{name}/browser/pages
curl http://localhost:18888/sandboxes/my-browser/browser/pages
{"pages": ["default", "docs"]}
POST /sandboxes/{name}/browser/pages/{page}/goto
curl -X POST http://localhost:18888/sandboxes/my-browser/browser/pages/default/goto \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com"}'
{
  "snapshot": "- document \"Example Domain\":\n  - heading \"Example Domain\" [level=1] [ref=e1]\n  ...",
  "url": "https://example.com/",
  "title": "Example Domain",
  "refs": ["e1", "e2"]
}

Request body:

Field Type Required Description
url string Yes URL to navigate to

Get ARIA Snapshot

GET /sandboxes/{name}/browser/pages/{page}/snapshot
curl http://localhost:18888/sandboxes/my-browser/browser/pages/default/snapshot

Returns the current ARIA snapshot without navigating. Same response format as goto.

Click Element

POST /sandboxes/{name}/browser/pages/{page}/click
curl -X POST http://localhost:18888/sandboxes/my-browser/browser/pages/default/click \
  -H "Content-Type: application/json" \
  -d '{"ref": "e2"}'

Returns a new ARIA snapshot after clicking.

Request body:

Field Type Required Description
ref string No Ref ID from ARIA snapshot
selector string No CSS selector (fallback)

Fill Input

POST /sandboxes/{name}/browser/pages/{page}/fill
curl -X POST http://localhost:18888/sandboxes/my-browser/browser/pages/default/fill \
  -H "Content-Type: application/json" \
  -d '{"ref": "e3", "value": "search query"}'

Returns a new ARIA snapshot after filling.

Request body:

Field Type Required Description
value string Yes Text to type
ref string No Ref ID from ARIA snapshot
selector string No CSS selector (fallback)

Screenshot

POST /sandboxes/{name}/browser/pages/{page}/screenshot

Returns a PNG screenshot as base64.

Evaluate JavaScript

POST /sandboxes/{name}/browser/pages/{page}/evaluate
curl -X POST http://localhost:18888/sandboxes/my-browser/browser/pages/default/evaluate \
  -H "Content-Type: application/json" \
  -d '{"expression": "document.title"}'

Get Page Content

GET /sandboxes/{name}/browser/pages/{page}/content

Returns raw page content (title, text, links) — similar to v1 goto format.

Close Page

DELETE /sandboxes/{name}/browser/pages/{page}
curl -X DELETE http://localhost:18888/sandboxes/my-browser/browser/pages/default

Browser Events

GET /sandboxes/{name}/browser/events
curl "http://localhost:18888/sandboxes/my-browser/browser/events?offset=0&limit=50"
[
  {"seq": 1, "type": "page.navigated", "page": "default", "ts": "2026-02-10T12:00:00Z"},
  {"seq": 2, "type": "page.clicked", "page": "default", "ts": "2026-02-10T12:00:01Z"}
]

Query parameters:

Field Type Required Description
offset integer No Start from this sequence number (default: 0)
limit integer No Max events to return (default: 100)

Durable Objects

List Objects

GET /objects
curl http://localhost:18888/objects
{
  "success": true,
  "data": [
    {
      "id": "019abc12-...",
      "class": "Counter",
      "object_id": "counter-1",
      "status": "active",
      "sandbox": "my-sandbox",
      "storage": {"count": 42},
      "idle_timeout_seconds": 300,
      "created_at": "2026-02-18T12:00:00Z",
      "updated_at": "2026-02-18T12:00:00Z"
    }
  ]
}

Create Object

POST /objects
curl -X POST http://localhost:18888/objects \
  -H "Content-Type: application/json" \
  -d '{"class": "Counter", "object_id": "counter-1", "sandbox": "my-sandbox"}'

Request body:

Field Type Required Description
class string Yes Object class name
object_id string Yes Unique object identifier within the class
sandbox string No Sandbox to bind to (validated if provided)
storage object No Initial storage state
idle_timeout_seconds integer No Seconds before hibernation (default: 300)

Get Object

GET /objects/{id}
curl http://localhost:18888/objects/019abc12-...

Update Object

PATCH /objects/{id}
curl -X PATCH http://localhost:18888/objects/019abc12-... \
  -H "Content-Type: application/json" \
  -d '{"storage": {"count": 99}}'

Request body:

Field Type Required Description
storage object No Replace storage state
status string No Set status (active, hibernating)

Delete Object

DELETE /objects/{id}
curl -X DELETE http://localhost:18888/objects/019abc12-...

Call Object Method

POST /objects/{class}/{object_id}/call/{method}
curl -X POST http://localhost:18888/objects/Counter/counter-1/call/increment \
  -H "Content-Type: application/json" \
  -d '{"amount": 1}'

Auto-creates the object if it does not exist. Wakes from hibernation if needed. The request body is passed as method arguments.

Schedules

List Schedules

GET /schedules
curl http://localhost:18888/schedules
{
  "success": true,
  "data": [
    {
      "id": "019abc12-...",
      "name": "daily-cleanup",
      "cron": "0 0 * * *",
      "method": "cleanup",
      "args": {},
      "status": "active",
      "created_at": "2026-02-18T12:00:00Z",
      "updated_at": "2026-02-18T12:00:00Z"
    }
  ]
}

Create Schedule

POST /schedules
curl -X POST http://localhost:18888/schedules \
  -H "Content-Type: application/json" \
  -d '{"name": "daily-cleanup", "cron": "0 0 * * *", "method": "cleanup"}'

Request body:

Field Type Required Description
name string Yes Schedule name
method string Yes Method to invoke
cron string No Cron expression (mutually exclusive with fire_at)
fire_at string No One-shot fire time in RFC3339 format
args object No Method arguments
target_class string No Target durable object class
target_object_id string No Target durable object ID
target_orchestration string No Target orchestration

Get Schedule

GET /schedules/{id}
curl http://localhost:18888/schedules/019abc12-...

Delete Schedule

DELETE /schedules/{id}
curl -X DELETE http://localhost:18888/schedules/019abc12-...

Durable Stores

List Stores

GET /stores
curl http://localhost:18888/stores
{
  "success": true,
  "data": [
    {
      "id": "019abc12-...",
      "name": "my-db",
      "kind": "sqlite",
      "sandbox": "my-sandbox",
      "config": {},
      "created_at": "2026-02-18T12:00:00Z",
      "updated_at": "2026-02-18T12:00:00Z"
    }
  ]
}

Create Store

POST /stores
curl -X POST http://localhost:18888/stores \
  -H "Content-Type: application/json" \
  -d '{"name": "my-db", "kind": "sqlite", "sandbox": "my-sandbox"}'

Request body:

Field Type Required Description
name string Yes Store name
kind string Yes Engine type: sqlite, kv, queue
sandbox string No Sandbox to bind to (validated if provided)
config object No Engine-specific configuration

Get Store

GET /stores/{id}
curl http://localhost:18888/stores/019abc12-...

Delete Store

DELETE /stores/{id}
curl -X DELETE http://localhost:18888/stores/019abc12-...

Query Store

Run a read-only query against a store.

POST /stores/{id}/query
curl -X POST http://localhost:18888/stores/019abc12-.../query \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT * FROM users WHERE active = ?", "params": [true]}'
{
  "success": true,
  "data": {
    "columns": ["id", "name", "active"],
    "rows": [[1, "Alice", true]],
    "row_count": 1
  }
}

Execute Store

Run a write statement against a store.

POST /stores/{id}/execute
curl -X POST http://localhost:18888/stores/019abc12-.../execute \
  -H "Content-Type: application/json" \
  -d '{"sql": "INSERT INTO users (name, active) VALUES (?, ?)", "params": ["Bob", true]}'
{
  "success": true,
  "data": {
    "rows_affected": 1
  }
}

Error Responses

{
  "success": false,
  "error": "Sandbox 'missing' not found"
}
Status Code Meaning
200 Success
201 Created
400 Bad request (validation error)
401 Unauthorized (missing/invalid API key)
404 Not found
500 Internal server error