API mode

Run the container as an HTTP API server with workspace management, file operations, and optional authentication. It includes the OpenAI-compatible adapter and can mount an optional MCP server.

# docker-compose.yml
services:
  claudebox:
    image: psyb0t/claudebox:latest
    init: true
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      - CLAUDEBOX_API_MODE=1
      - CLAUDEBOX_API_MODE_TOKEN=${CLAUDEBOX_API_MODE_TOKEN:?set this in .env}
      - CLAUDEBOX_AVAILABLE_MODELS=haiku,sonnet,opus,opusplan
      - CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN:?set this in .env}
    volumes:
      - ./claude-state:/home/aicode/.aicodebox
      - /your/projects:/workspace
    mem_limit: 2g
    cpus: 2
    pids_limit: 512
    logging:
      driver: local
      options:
        max-size: 10m
        max-file: "3"

The example publishes only to loopback and intentionally omits /var/run/docker.sock. Put the API behind a reverse proxy with TLS and authentication when clients are not on the same host. Adding the Docker socket gives Claude host container control.

Variable Description Default
CLAUDEBOX_API_MODE Set to 1 to start in API server mode (none)
CLAUDEBOX_API_MODE_PORT Port the API server listens on 8080
CLAUDEBOX_API_MODE_TOKEN Bearer token for API authentication (if unset, no auth is required) (none)
CLAUDEBOX_AVAILABLE_MODELS Comma separated model IDs returned by /openai/v1/models. Claudebox has a built-in list, but set it explicitly to change the advertised models. This is not a model-access restriction. haiku,sonnet,opus,opusplan
DEBUG Set to 1 or true for structured JSON debug logging (none)

Legacy CLAUDE_MODE_API, CLAUDE_MODE_API_PORT, CLAUDE_MODE_API_TOKEN are still accepted as fallbacks.

The API server outputs structured JSON logs (timestamp, level, logger, function name, line number, and file) for every request, error, and lifecycle event.

API endpoints

POST /run: send a prompt to Claude Code and get a JSON response:

curl -X POST http://localhost:8080/run \
  -H "Authorization: Bearer your-secret-token" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "what does this repo do", "workspace": "myproject"}'
Field Type Description Default
prompt string The prompt to send to Claude Code (required)
workspace string Subpath under /workspace (e.g., myproject resolves to /workspace/myproject) /workspace
model string Model alias or full model name (see Model Selection) account default
systemPrompt string Replace the default system prompt entirely (none)
appendSystemPrompt string Append text to the default system prompt without replacing it (none)
jsonSchema object A JSON Schema for the final output. The server validates it and retries invalid output; persistent validation failure returns an error (none)
thinking string Claude reasoning level. low, medium, high, xhigh, and max map to Claude Code’s --effort flag. off or none keep Claude Code’s default. (none)
eventMode string auto, none, or full. full returns complete native Claude stream records auto
outputFormat string Legacy compatibility: json-verbose selects full events while eventMode is auto; text and json do not change the response shape (none)
noContinue bool If true, start a fresh session instead of continuing the previous one false
resume string Resume a specific session by its session ID (none)
fireAndForget bool Submit in the background and mark the immediate acknowledgement with fireAndForget: true false
extraArgs string[] Additional Claude CLI arguments. Only allow trusted callers to set these (none)
toolsAllowlist string[] Passed as Claude Code’s --allowedTools; not a sandbox or an enforced tool restriction with permission bypass (none)
noTools bool Disable the agent’s internal tools false
timeoutSeconds integer Run timeout in seconds (none)
includeRaw bool Include raw stdout and stderr. Failure diagnostics can include stderr regardless false
async bool If true, return immediately with a runId and run in the background false

Accepted runs receive a runId that identifies the run. Authentication and request-validation errors are not run results.

The Claude adapter always requests its complete native stream, including partial assistant messages, tool use, tool results, hook events, subagent text, and final usage. Set "eventMode": "full" to return those records. Each response record has the stable envelope {sequence, attempt, backend, eventType, event}. The nested event is the original Claude record with no field removal or tool-result truncation. "eventMode": "none" returns only the final result. The default "auto" keeps the historical behavior of including events for a jsonSchema request or "outputFormat": "json-verbose". "outputFormat": "text" and "outputFormat": "json" remain accepted legacy inputs but do not change the response serialization.

jsonSchema validates only the final JSON output and can be combined with either event mode.

/run returns completed application/json, not a live event stream. It returns 409 if that workspace path is already busy. Workspace path checks confine the file API, not the Claude subprocess. The agent can access other files and processes permitted by the container; workspace subpaths do not isolate untrusted tenants.

Async runs: when "async": true is set, the request returns immediately with a run ID:

# fire off an async run
curl -X POST http://localhost:8080/run \
  -H "Authorization: Bearer token" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "refactor this entire codebase", "workspace": "myproject", "async": true}'
# → {"runId": "abc123", "workspace": "/workspace/myproject", "status": "running", "fireAndForget": false}

# poll for the result
curl "http://localhost:8080/run/result?runId=abc123" -H "Authorization: Bearer token"
# while running → {"runId": "abc123", "workspace": "/workspace/myproject", "status": "running"}
# when done    → full result JSON with runId + workspace injected (see below)

Completed results are cached until first read: once you fetch a completed result, it is purged from the cache. Results that are never read are automatically purged after 6 hours. Failed and cancelled results are also returned once and purged.

GET /run/result?runId=X: poll for the result of an async (or any) run:

Status Response
running {"runId": "...", "workspace": "...", "status": "running"}
completed Full result JSON with runId and workspace injected (then purged from cache)
failed {"runId": "...", "workspace": "...", "status": "failed", "error": "..."} (then purged)
cancelled {"runId": "...", "workspace": "...", "status": "cancelled"} (then purged)

Returns 404 if the run ID is not found (never existed, already read, or expired).

Completed result example:

{
  "runId": "abc123",
  "workspace": "/workspace/myproject",
  "status": "completed",
  "exitCode": 0,
  "text": "the response text",
  "usage": { "input_tokens": 100, "output_tokens": 50 },
  "sessionId": "..."
}

GET /files/{path}: list a directory or download a file:

curl "http://localhost:8080/files" -H "Authorization: Bearer token"                         # list workspace root
curl "http://localhost:8080/files/myproject/src" -H "Authorization: Bearer token"           # list a subdirectory
curl "http://localhost:8080/files/myproject/src/main.py" -H "Authorization: Bearer token"   # download a file

Directory listing response:

{
  "path": "myproject/src",
  "entries": [
    { "name": "main.py", "type": "file", "size": 1234 },
    { "name": "utils", "type": "dir" }
  ]
}

File download returns raw file content with appropriate content type.

PUT /files/{path}: upload a file (parent directories are created automatically):

curl -X PUT "http://localhost:8080/files/myproject/src/main.py" \
  -H "Authorization: Bearer token" --data-binary @main.py
# → {"status": "ok", "path": "/workspace/myproject/src/main.py", "size": 1234}

DELETE /files/{path}: delete a file:

curl -X DELETE "http://localhost:8080/files/myproject/src/old.py" -H "Authorization: Bearer token"
# → {"status": "ok", "path": "/workspace/myproject/src/old.py"}

GET /healthz: health check endpoint (no authentication required):

{ "ok": true, "adapter": "claude" }

GET /status: returns busy workspaces and all tracked runs (running, completed, failed, cancelled):

{
  "busyWorkspaces": ["/workspace/myproject"],
  "runs": [
    {
      "runId": "abc123",
      "workspace": "/workspace/myproject",
      "status": "running"
    }
  ]
}

DELETE /run/{runId}: cancel a running Claude process by run ID:

curl -X DELETE "http://localhost:8080/run/abc123" -H "Authorization: Bearer token"
# → {"runId": "abc123", "status": "cancelled"}

All file paths are relative to /workspace. Path traversal attempts outside the workspace root are blocked and return a 400 error.

OpenAI-compatible endpoints

Claudebox adapts the chat/completions protocol to Claude Code. Requests can run the agent’s file and shell tools, subject to the request’s tool settings and the container’s permissions.

GET /openai/v1/models: list available models:

curl http://localhost:8080/openai/v1/models -H "Authorization: Bearer your-secret-token"
# {"object":"list","data":[{"id":"haiku",...},{"id":"sonnet",...},{"id":"opus",...},{"id":"opusplan",...}]}

POST /openai/v1/chat/completions: chat completions (streaming and non-streaming):

# non-streaming
curl -X POST http://localhost:8080/openai/v1/chat/completions \
  -H "Authorization: Bearer your-secret-token" \
  -H "Content-Type: application/json" \
  -d '{"model":"haiku","messages":[{"role":"user","content":"hello"}]}'

# streaming (SSE)
curl -X POST http://localhost:8080/openai/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"haiku","messages":[{"role":"user","content":"hello"}],"stream":true}'

Model names: use the same aliases as the CLI (haiku, sonnet, opus, opusplan). Provider prefixes are stripped automatically: claudebox/haiku becomes haiku, openai/sonnet becomes sonnet.

System messages: messages with role: "system" are extracted and passed to Claude Code as --system-prompt.

Reasoning effort: reasoning_effort maps to Claude Code’s --effort flag. low, medium, high, xhigh, and max pass through, minimal maps to low, and none or off keeps Claude Code’s default. Any other value is rejected. The thinking field on /run and the MCP run_prompt tool follow the same rules.

Client-executed tools: standard tools and tool_choice are supported. A tool-call turn returns OpenAI tool_calls; the client runs the selected function and sends the role: "tool" result in the next request. Internal agent tools default off for that turn. Send x-aicodebox-no-tools: 0 to keep them enabled.

Structured output: standard response_format supports json_object and json_schema. It composes with client-executed tools: tool-call turns are not schema-checked, and the final answer is validated against the requested schema.

Ignored fields: temperature, max_tokens, and unsupported OpenAI fields are accepted without effect.

Message handling:

  • Single user message: sent directly as the prompt to Claude Code. This is the fast path with no overhead.
  • Multi-turn conversations: the full messages array is serialized to a JSON file in the workspace (_oai_uploads/conv_<id>.json). Claude Code reads the file and responds to the last user message, preserving the full conversation context.
  • Multimodal content: base64-encoded images and image URLs in message content are automatically downloaded or decoded and saved to the workspace. The content blocks are replaced with local file paths so Claude Code can access the images directly.

Streaming: with "stream": true, plain chat returns standard SSE in message-level chunks. This adapter extracts completed assistant messages for standard OpenAI text chunks. Requests using client-executed tools or schema-constrained output buffer the result before sending the SSE response. Native partial records remain available through the opt-in extension below.

To receive every Claude native stream record too, add "stream_options": {"include_aicodebox_events": true}. The response emits a named aicodebox.native SSE event before normal OpenAI chunks. Its JSON payload is {sequence, attempt, backend, eventType, event} and does not alter the standard OpenAI chunk data. This remains opt-in so strict OpenAI SSE parsers can continue consuming only standard chunks.

For large inputs or outputs, upload files with PUT /files/..., reference their paths in the prompt, and download the resulting files with GET /files/....

Custom headers for claudebox-specific behavior:

Header Description
X-Aicodebox-Workspace Workspace subpath under /workspace. X-Claude-Workspace remains an alias.
X-Aicodebox-Continue Set to 1, true, or yes to continue the previous session. X-Claude-Continue remains an alias.
X-Aicodebox-Append-System-Prompt Text to append to the system prompt. X-Claude-Append-System-Prompt remains an alias.
X-Aicodebox-Json-Schema JSON Schema fallback when the request has no response_format.
X-Aicodebox-Resume Specific session ID to resume.
X-Aicodebox-Extra-Args JSON array or comma-separated CLI arguments.
X-Aicodebox-Timeout-Seconds Positive timeout in seconds.
X-Aicodebox-Tools-Allowlist JSON array or comma-separated names passed as --allowedTools. Not a security boundary with permission bypass.
X-Aicodebox-No-Tools Boolean that disables internal agent tools.

LiteLLM integration example:

import litellm

response = litellm.completion(
    model="claudebox/haiku",
    messages=[{"role": "user", "content": "hello"}],
    api_base="http://localhost:8080/openai/v1",
    api_key="your-secret-token",  # or any string if no API token is configured
)
print(response.choices[0].message.content)

MCP server

Set CLAUDEBOX_MCP_MODE=1 to mount MCP at /mcp/ on the API port. Set CLAUDEBOX_MCP_MODE_TOKEN separately; the API token does not protect MCP. The prompt tool is run_prompt, not the old claude_run name. See MCP mode for tools, client configuration, standalone placement, and reverse-proxy allowlists.