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:
Multiple keys can be configured for key rotation or multi-tenant setups. Any valid key will authenticate the request.
Endpoints
Health Check
Server Status
{
"success": true,
"data": {"version": "0.15.0", "backend": "docker", "api_key_configured": false}
}
Server Statistics
{
"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)
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
Removes sandboxes that have exceeded their time-to-live.
Run Command
Execute a command in a temporary sandbox.
curl -X POST http://localhost:18888/run \
-H "Content-Type: application/json" \
-d '{"command": ["python3", "-c", "print(1+1)"]}'
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.
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
# 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
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
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
{
"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
Execute in Sandbox
curl -X POST http://localhost:18888/sandboxes/my-sandbox/exec \
-H "Content-Type: application/json" \
-d '{"command": ["ls", "-la"]}'
Stop Sandbox
Delete Sandbox
Extend TTL
Extend a sandbox's time-to-live.
curl -X POST http://localhost:18888/sandboxes/my-sandbox/extend \
-H "Content-Type: application/json" \
-d '{"by": "1h"}'
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.
Reconcile Lifecycle Policies
Applies lifecycle policies across all sandboxes (or previews actions).
# 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
curl -X PUT http://localhost:18888/sandboxes/my-sandbox/files/tmp/hello.txt \
-H "Content-Type: application/json" \
-d '{"content": "hello world"}'
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
Binary files are returned as base64 with "encoding": "base64".
Delete File
Sandbox Logs
Retrieve audit log entries for a specific sandbox.
{
"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.
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
{
"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
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
Returns snapshot details (same format as list).
Delete Snapshot
Restore Snapshot
curl -X POST http://localhost:18888/snapshots/checkpoint-1/restore \
-H "Content-Type: application/json" \
-d '{"as_name": "restored-sandbox"}'
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
Starts the in-sandbox browser server. Called automatically by other browser endpoints if needed.
List Pages
Navigate (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
Returns the current ARIA snapshot without navigating. Same response format as goto.
Click Element
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
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
Returns a PNG screenshot as base64.
Evaluate JavaScript
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
Returns raw page content (title, text, links) — similar to v1 goto format.
Close Page
Browser Events
[
{"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
{
"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
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
Update Object
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
Call Object 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
{
"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
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
Delete Schedule
Durable Stores
List 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
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
Delete Store
Query Store
Run a read-only query against a store.
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.
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]}'
Error Responses
| 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 |