HTTP API
Run the warden binary as a headless server and make HTTP requests to /api/v1/*. This works from any language.
# Start the server (default: localhost:8090)./warden
# Or with a custom addressADDR=0.0.0.0:9000 ./wardenAll endpoints are under /api/v1/. See the API Reference for full endpoint documentation.
Example: List projects
Section titled “Example: List projects”curl http://localhost:8090/api/v1/projectsTypeScript
Section titled “TypeScript”const response = await fetch("http://localhost:8090/api/v1/projects");const projects = await response.json();Python
Section titled “Python”import requests
response = requests.get("http://localhost:8090/api/v1/projects")projects = response.json()Example: Create a project and container
Section titled “Example: Create a project and container”Creating a project is a two-step flow: register the project, then create its container.
# Step 1: Register the projectcurl -X POST http://localhost:8090/api/v1/projects \ -H "Content-Type: application/json" \ -d '{"name": "my-project", "projectPath": "/home/user/project"}'
# Step 2: Create the container (using projectId from step 1)curl -X POST http://localhost:8090/api/v1/projects/a1b2c3d4e5f6/claude-code/container \ -H "Content-Type: application/json" \ -d '{ "name": "my-project", "projectPath": "/home/user/project", "networkMode": "restricted", "allowedDomains": ["github.com", "npmjs.org"], "envVars": {"ANTHROPIC_API_KEY": "sk-ant-..."}, "enabledRuntimes": ["node", "python"] }'TypeScript
Section titled “TypeScript”// Step 1: Register the projectconst addResponse = await fetch("http://localhost:8090/api/v1/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "my-project", projectPath: "/home/user/project", }),});const { projectId } = await addResponse.json();
// Step 2: Create the containerconst createResponse = await fetch( `http://localhost:8090/api/v1/projects/${projectId}/claude-code/container`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "my-project", projectPath: "/home/user/project", networkMode: "restricted", allowedDomains: ["github.com", "npmjs.org"], envVars: { ANTHROPIC_API_KEY: "sk-ant-..." }, enabledRuntimes: ["node", "python"], }), },);const container = await createResponse.json();Python
Section titled “Python”import requests
BASE = "http://localhost:8090/api/v1"
# Step 1: Register the projectresult = requests.post(f"{BASE}/projects", json={ "name": "my-project", "projectPath": "/home/user/project",}).json()project_id = result["projectId"]
# Step 2: Create the containercontainer = requests.post( f"{BASE}/projects/{project_id}/claude-code/container", json={ "name": "my-project", "projectPath": "/home/user/project", "networkMode": "restricted", "allowedDomains": ["github.com", "npmjs.org"], "envVars": {"ANTHROPIC_API_KEY": "sk-ant-..."}, "enabledRuntimes": ["node", "python"], },).json()Example: Real-time events (SSE)
Section titled “Example: Real-time events (SSE)”Subscribe to GET /api/v1/events for real-time state updates across all projects.
curl -N http://localhost:8090/api/v1/eventsTypeScript
Section titled “TypeScript”const eventSource = new EventSource("http://localhost:8090/api/v1/events");
eventSource.addEventListener("worktree_state", (event) => { const data = JSON.parse(event.data); console.log(`Worktree ${data.worktreeId}: ${data.state}`);});
eventSource.addEventListener("project_state", (event) => { const data = JSON.parse(event.data); console.log( `Project ${data.projectId}: cost=${data.totalCost}, needsInput=${data.needsInput}`, );});Python
Section titled “Python”import sseclientimport requests
response = requests.get("http://localhost:8090/api/v1/events", stream=True)client = sseclient.SSEClient(response)
for event in client.events(): print(f"{event.event}: {event.data}")Example: Terminal lifecycle
Section titled “Example: Terminal lifecycle”Terminal interaction is a two-step flow: connect (start the agent process), then attach via WebSocket.
PROJECT_ID="a1b2c3d4e5f6"AGENT="claude-code"WORKTREE="main"
# Connect terminal (starts tmux session + agent)curl -X POST "http://localhost:8090/api/v1/projects/$PROJECT_ID/$AGENT/worktrees/$WORKTREE/connect"
# Attach via WebSocket (use a WebSocket client — binary frames for PTY I/O)# wscat -c "ws://localhost:8090/api/v1/projects/$PROJECT_ID/$AGENT/ws/$WORKTREE"
# Disconnect (notify server the viewer closed)curl -X POST "http://localhost:8090/api/v1/projects/$PROJECT_ID/$AGENT/worktrees/$WORKTREE/disconnect"
# Or kill (terminate the agent process)curl -X POST "http://localhost:8090/api/v1/projects/$PROJECT_ID/$AGENT/worktrees/$WORKTREE/kill"TypeScript
Section titled “TypeScript”const BASE = "http://localhost:8090/api/v1";const projectId = "a1b2c3d4e5f6";const agent = "claude-code";const worktree = "main";
// Connect terminalawait fetch( `${BASE}/projects/${projectId}/${agent}/worktrees/${worktree}/connect`, { method: "POST" },);
// Attach via WebSocketconst ws = new WebSocket( `ws://localhost:8090/api/v1/projects/${projectId}/${agent}/ws/${worktree}`,);
// Binary frames = PTY data, text frames = control messagesws.onmessage = (event) => { if (event.data instanceof Blob) { // Terminal output } else { // Control message (e.g. resize acknowledgment) }};
// Send resizews.send(JSON.stringify({ type: "resize", cols: 120, rows: 40 }));
// Send terminal input as binaryws.send(new TextEncoder().encode("ls -la\n"));Error handling
Section titled “Error handling”All error responses include a machine-readable code field:
{ "error": "Project name already in use", "code": "NAME_TAKEN"}TypeScript
Section titled “TypeScript”const response = await fetch("http://localhost:8090/api/v1/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "my-project", projectPath: "/path" }),});
if (!response.ok) { const error = await response.json(); console.error(`${error.code}: ${error.error}`);}Python
Section titled “Python”response = requests.post( "http://localhost:8090/api/v1/projects", json={"name": "my-project", "projectPath": "/path"},)
if not response.ok: error = response.json() print(f"{error['code']}: {error['error']}")See reference/error-handling.md for the full error code table.