REST API

A versioned HTTP API for launching and managing boxes — for IDEs, scripts, and remote hubs

The hub serves a small, versioned REST API under /api/v1 on the same port as the hub UI and relay (http://127.0.0.1:8787 locally). It's for programmatic callers — IDE integrations, scripts, macOS apps, etc. — and is the way to drive AgentBox when the hub runs on a separate host (a control plane), where there's no local CLI to shell out to.

Local vs. remote

On your own machine you can also just call the CLI with --json (agentbox list --json, agentbox status <box> --json). The API is what you reach for across a network, or when you want one stable HTTP contract instead of spawning a process per call.

Base URL and versioning

http://127.0.0.1:8787/api/v1

Every path is prefixed with /api/v1. The version is in the path so future breaking changes can ship as /api/v2 without disturbing existing clients.

Auth

Every endpoint except /health, /openapi.json, and /docs requires the hub token as a Bearer credential:

TOKEN=$(cat ~/.agentbox/hub/token)
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8787/api/v1/boxes

The token is the same per-machine secret the hub prints on boot (and stores at ~/.agentbox/hub/token, mode 0600). Requests without a valid token get a JSON 401 — never a redirect, so non-browser clients work cleanly. On a shared (password-protected) hub, /api/v1 accepts the logged-in session; dedicated API keys arrive with the hosted deployment.

Response shape

Success responses return the resource or collection directly. Errors are always:

{ "error": { "code": "not_found", "message": "box not found: abc123" } }

with an HTTP status that matches the code (400 invalid_request, 401 unauthorized, 404 not_found, 409 conflict, 503 backend_unavailable).

Endpoints

Endpoints are grouped by topic — the same sections you'll see in the sidebar of the interactive reference at GET /docs.

System

Method + pathDescription
GET /healthLiveness + API version (no auth).
GET /openapi.jsonThe OpenAPI 3.1 spec (no auth).
GET /docsInteractive API reference (no auth).

Boxes

Method + pathDescription
GET /boxesList every box (normalized view).
GET /boxes/{id}One box.
POST /boxesCreate a box — returns 202 { jobId }. Body: { projectId, agent, provider?, name?, prompt?, fromBranch?, setupWizard? }. agent: "none" just creates the box without starting an agent (prompt ignored). provider defaults to docker; a cloud provider must be configured on the host (see GET /providers). fromBranch bases the box's per-box branch on a chosen ref (branch/tag/SHA) instead of the project's HEAD — validated against the host repo. setupWizard: true seeds the agent's first turn to generate an agentbox.yaml (for projects that have none); inert for agent: "none".
POST /boxes/{id}/pausePause a running box.
POST /boxes/{id}/resumeResume a paused box.
POST /boxes/{id}/stopStop a box.
POST /boxes/{id}/destroyDestroy a box (container + volumes).

Box git

Method + pathDescription
GET /boxes/{id}/gitLive git summary of the box's worktree: { ok, branch, dirty, ahead, behind }.
POST /boxes/{id}/git/{op}Git op on the box branch (checkout, branch, pull, push, push-host). Body per op: checkout {branch}; branch {name, from?} (create+switch a new agentbox/* branch); pull {remote?, ffOnly?}; push {remote?, force?}; push-host {as?, force?} (land in the host repo only — publishes nothing). Returns { ok, stdout, stderr }.
GET /boxes/{id}/branchesThe box project's branches (local + remote) and current HEAD, for the box git-panel branch picker: { current, branches }.

Box services

Method + pathDescription
GET /boxes/{id}/servicesThe box's agentbox.yaml service/task/port status (source: live | persisted | unavailable).
POST /boxes/{id}/services/restartRestart one service ({ name }) or every service (empty body).

Projects

Method + pathDescription
GET /projectsRegistered projects (create targets). Each carries currentBranch (the default base) and needsSetup (no agentbox.yaml + no default snapshot).
POST /projectsRegister a folder as a project. Body: { path } (absolute).
GET /projects/{id}/branchesThe project's branches (local + remote) and current HEAD, for the create base-branch picker: { current, branches }.
DELETE /projects/{id}Unregister an empty project (0 boxes). Folder/files on disk are untouched; 409 if it still has boxes.

Providers

Method + pathDescription
GET /providersSandbox providers with credential + baked status on this host: { id, label, configured, hasCredentials?, jobId?, reason? }. configured = base image baked (usable for create); hasCredentials = credentials present (docker always); jobId = an in-flight bake, if any. Add ?freshness=1 to also report base-image staleness per provider: baseStatus (fresh | stale | unprepared | unknown) + baseStaleReason when stale — stale means the baked snapshot is out of date and agentbox prepare --provider <id> should be re-run. Freshness loads provider code and hashes the runtime build context (memoized ~60s), so it's kept off the default fast path; it's populated only on the in-process host topology (omitted on the hosted plane).
POST /providers/{id}/credentialsSet a provider's credentials (validated against the cloud, then saved to ~/.agentbox/secrets.env). Body is provider-specific string fields: { apiKey } (e2b/daytona), { token } (hetzner), { token, teamId?, projectId? } (vercel). Never echoes secret values. Host-topology only (503 on the hosted plane).
POST /providers/{id}/prepareBake a provider's base image — returns 202 { jobId }. Optional body { force?, claudeInstall? }. Progress streams over GET /jobs/{id}/logs (same as create). Prechecks the host (docker daemon; hetzner ssh/scp; credentials present) and reuses an in-flight bake for the same provider.

Approvals

Method + pathDescription
GET /approvalsPending host-action approvals.
POST /approvals/{id}/answerAnswer an approval. Body: { answer: "y" | "n" }.

Jobs

Method + pathDescription
GET /jobs/{id}Create-job status (queued/running/done/failed).
GET /jobs/{id}/logsStream the build log (SSE).

The box view is provider-agnostic: { id, projectId, repo, branch, task, agent, status, createdAt, lastActivity, host, commits, filesTouched, error }, where status is one of running | paused | stopped | creating | error.

Creating a box

Creation is asynchronous — it enqueues a build job and returns immediately:

# Register the folder once (skip if it's already a project):
curl -X POST -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"path":"/abs/path/to/repo"}' \
  http://127.0.0.1:8787/api/v1/projects

# Create a box for it (grab the projectId from GET /projects):
curl -X POST -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"projectId":"<id>","agent":"claude","name":"feature-x"}' \
  http://127.0.0.1:8787/api/v1/boxes
# -> 202 { "jobId": "..." }

A box being built shows up in GET /boxes with status: "creating" right away, then flips to running once the container is up.

Streaming logs (SSE)

GET /jobs/{id}/logs is a Server-Sent Events stream: an open event, then a log event per line, then a terminal end event with the final status.

curl -N -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:8787/api/v1/jobs/<jobId>/logs
event: open
data: {"id":"<jobId>"}

event: log
data: "... building image ..."

event: end
data: {"status":"done"}

Installing providers & baking images

Before a cloud provider can host boxes it needs two things on the host: its credentials and its baked base image. Both are drivable over the API (the hub settings UI uses exactly these calls), so you never have to drop to a terminal:

# 1. Save credentials (validated against the cloud, then written to secrets.env):
curl -X POST -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"apiKey":"e2b_..."}' \
  http://127.0.0.1:8787/api/v1/providers/e2b/credentials
# -> 200 { "ok": true }   (a rejected token -> 400 with the reason)

# 2. Bake the base image (async — stream the build like a create job):
curl -X POST -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{}' http://127.0.0.1:8787/api/v1/providers/e2b/prepare
# -> 202 { "jobId": "..." }
curl -N -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8787/api/v1/jobs/<jobId>/logs

Once the bake finishes, GET /providers reports the provider as configured: true and it becomes a valid provider for POST /boxes. Docker needs no credentials; its base self-heals, so a bake there is optional (a force rebuild).

Host capability

These two endpoints run on the host that owns Docker/SSH and writes ~/.agentbox — the local or full-host hub. On the hosted (serverless) plane they return 503.

Exploring the API

GET /api/v1/docs renders the full spec (an interactive reference), and GET /api/v1/openapi.json returns the raw OpenAPI 3.1 document you can feed to a client generator or an IDE's HTTP client.

On this page