HTTP API

AICODEBOX_API_MODE=1 starts a FastAPI server on port 8080 (AICODEBOX_API_MODE_PORT). It serves the routes on this page, the OpenAI-compatible endpoint, and the MCP server at /mcp/ when AICODEBOX_MCP_MODE=1. The code is in aicodebox/modes/api/.

API mode refuses to start without a model list. Set AICODEBOX_AVAILABLE_MODELS to the model IDs your provider serves, or have the adapter declare available_models. /openai/v1/models has no fallback, because the adapter name is not a model name.

Authentication

Set AICODEBOX_API_MODE_TOKEN and send it on every request:

curl -s http://localhost:8080/run \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"prompt": "List the files in this workspace."}'

One token, compared exactly. A missing header gives 401 missing bearer token, a wrong one 401 invalid bearer token. With the variable empty or unset, every route is open. /healthz never asks for a token.

Routes

Method and path Auth Purpose
POST /run yes Run the agent, synchronously or in the background.
GET /run/result?runId=<id> yes Poll a background run.
DELETE /run/{id} yes Kill a run.
GET /files, GET /files/{path} yes List a directory or download a file.
PUT /files/{path} yes Write the raw request body to a file.
DELETE /files/{path} yes Delete a file.
GET /status yes Busy workspaces and tracked runs.
GET /healthz no Whether an adapter is configured.
POST /openai/v1/chat/completions, GET /openai/v1/models yes See OpenAI-compatible endpoint.
/mcp/ MCP token See MCP server.

POST /run

The body is JSON. Field names are camelCase; the snake_case names (system_prompt, json_schema and so on) are accepted too.

Field Type Default Meaning
prompt string required The instruction. Must not be empty.
workspace string root Subdirectory of AICODEBOX_WORKSPACE. Created if missing. Absolute paths and .. give a 400.
model string adapter default Model to use.
systemPrompt string   Replace the agent’s system prompt.
appendSystemPrompt string   Append to the system prompt.
jsonSchema object   Validate the answer as JSON against this schema, with retries.
eventMode auto, none, full auto Whether to return the agent’s native events. See Event retention.
outputFormat text, json, json-verbose   Legacy. Only json-verbose has an effect, and only while eventMode is auto.
noContinue boolean false Start a fresh session instead of continuing the last one in the workspace.
resume string   Resume a specific session ID.
async boolean false Return at once and run in the background.
fireAndForget boolean false Same as async. The response echoes the flag.
timeoutSeconds integer none Kill the agent after this many seconds.
thinking string   Reasoning level.
toolsAllowlist array of strings   Restrict the agent’s own tools.
noTools boolean false Disable the agent’s own tools.
extraArgs array of strings   Extra command-line arguments for the agent.
includeRaw boolean false Add stdout and stderr to the response.

What each field does depends on the adapter. The base passes them through in the RunRequest; see Adapter contract.

Response

A plain run returns:

{
  "runId": "3f0c9b2e6d0a4c1f9a7e2b5d8c4f6a1e",
  "workspace": "/workspace",
  "exitCode": 0,
  "text": "README.md\nsrc/"
}

Extra fields appear when they apply:

Field When
sessionId, usage With jsonSchema or eventMode: "full", when the adapter reports them.
json With jsonSchema, when an attempt produced valid JSON.
parseError With jsonSchema, when every attempt failed validation.
attempts With jsonSchema: [{index, usage, exitCode, parseError}], one entry per attempt.
jsonRetries With jsonSchema, the number of retries used, when there was at least one.
events With eventMode: "full".
stdout, stderr With includeRaw. stderr is always included when exitCode is not 0.

A non-zero exit code from the agent is not an HTTP error. The response is still 200 with exitCode and stderr set.

JSON schema retries

With jsonSchema, the run goes through the retry helper in aicodebox/shared/runner.py. It tries to read JSON out of the answer even with code fences or prose around it. If that fails or the value does not match the schema, it asks the agent again, up to three times, with the original task, the bad answer, the error and the schema. Retries stop early when an attempt exits non-zero or reports a provider error. usage is the sum over all attempts, because the provider bills every one.

{
  "prompt": "Inspect the workspace and return a short report.",
  "jsonSchema": {
    "type": "object",
    "properties": {
      "summary": {"type": "string"}
    },
    "required": ["summary"]
  }
}

Event retention

eventMode decides whether the response carries the agent’s own event stream:

eventMode Events returned
none Never, even with jsonSchema.
full Always.
auto (default) When jsonSchema is set or outputFormat is json-verbose. Kept for older callers.

Each record has a stable envelope around the untouched provider record:

{
  "sequence": 1,
  "attempt": 0,
  "backend": "your-agent",
  "eventType": "message_start",
  "event": {
    "type": "message_start"
  }
}

sequence counts across all attempts, attempt is the schema retry index, backend is the adapter name, and eventType is the record’s own type field. A stdout line that is not a JSON object is kept as {"line": "..."} with eventType raw_stdout. New callers should use eventMode, because a JSON schema and event retention are separate choices.

Background runs

With async or fireAndForget, POST /run answers at once:

{
  "runId": "3f0c9b2e6d0a4c1f9a7e2b5d8c4f6a1e",
  "workspace": "/workspace",
  "status": "running",
  "fireAndForget": false
}

Poll GET /run/result?runId=<id>. While the run is going it returns status: "running". When it ends, the response has status (completed, failed or cancelled) plus the same fields a synchronous run returns, or error for a failed run. A finished result is deleted the first time it is read, so a second poll gives 404. A sweep every ten minutes drops any run, read or not, six hours after it finished, or six hours after it started if it never finished. Up to eight background runs execute at once; more wait in a queue.

DELETE /run/{id} kills the agent process and returns {"runId": "...", "status": "cancelled"}, or 404 for an unknown ID. A run that already finished also answers cancelled, without changing its stored result.

One run per workspace

A workspace runs one agent at a time, across /run, the OpenAI endpoint and the mounted MCP run_prompt tool. A second request for a busy workspace gets 409 workspace <path> is busy with another run from /run, 409 workspace busy, retry later from the OpenAI endpoint, and an error result from run_prompt. Give parallel callers different workspace values.

Files

The file routes work on paths relative to AICODEBOX_WORKSPACE. A path that resolves outside it, including through a symlink, gives 400 path escapes workspace root.

Request Result
GET /files {"path": "/", "entries": [...]} for the root. Each entry has name, type (file or dir) and, for files, size.
GET /files/{path} The same listing for a directory, or the file’s bytes. 404 if missing.
PUT /files/{path} Writes the raw body, creating parent directories. Returns {"status": "ok", "path": ..., "size": <bytes>}. 400 if the path is a directory.
DELETE /files/{path} Removes a file. 400 for a directory, 404 if missing.
curl -s -X PUT http://localhost:8080/files/notes/todo.md \
  -H "Authorization: Bearer $TOKEN" \
  --data-binary @todo.md

Status and health

GET /status returns {"busyWorkspaces": [...], "runs": [{"runId", "workspace", "status"}]} for the runs the server still holds.

GET /healthz returns {"ok": true, "adapter": "<name>"}, or {"ok": false, "error": "..."} with status 200 when no adapter is configured. It does not start the agent.

Errors

Status Cause
400 Bad workspace path, or a file path outside the workspace.
401 Missing or wrong bearer token.
404 Unknown run ID, or a missing file.
409 The workspace is busy with another run.
422 Invalid body, or the adapter’s validate rejected the request.
500 The run raised an exception inside the server.