HTTP API
The local JSON API behind arena ui.
The local JSON API behind arena ui, served by agent_arena/web/server.py.
This API is unauthenticated by design. It binds to loopback and drives your filesystem and your API credit. Read ../security/threat-model.md before exposing it.
Base URL: http://localhost:8420.
Conventions
- Requests and responses are JSON. Bodies must be a JSON object.
- Bodies over 8 MB are rejected with 413.
- A project
namematches[a-z0-9][a-z0-9_-]{0,63}. - Every response carries
Content-Security-Policy,X-Content-Type-Options: nosniffandReferrer-Policy: no-referrer; API responses areno-store. - A request whose
Hostheader is not in the allow-list gets 403.
Endpoints
| Method | Path | Does |
|---|---|---|
| GET | /api/catalog |
Available models, scorers and wizard presets |
| GET | /api/projects |
List projects |
| POST | /api/projects |
Create a project |
| GET | /api/projects/{name} |
Project detail, config and preflight |
| PUT | /api/projects/{name} |
Update config |
| PUT | /api/projects/{name}/tests |
Replace test cases |
| POST | /api/projects/{name}/run |
Start a run; returns a job snapshot |
| GET | /api/projects/{name}/history |
Past runs and model trends |
| GET | /api/projects/{name}/result?run_id= |
A stored run (default: latest) |
| POST | /api/projects/{name}/whatif |
Re-rank stored results under new weights |
| GET | /api/jobs/{job_id} |
Job status |
| POST | /api/jobs/{job_id}/cancel |
Stop a sweep that is spending money |
| DELETE | /api/projects/{name} |
Delete a project — ?keep_results=&dry_run= |
| POST | /api/projects/{name}/duplicate |
Copy a project, excluding its results |
| POST | /api/projects/{name}/archive |
Hide it from the default listing |
| GET | /api/projects/{name}/runs |
Runs for a project — ?limit=&include_deleted= |
| DELETE | /api/projects/{name}/runs/{run_id} |
Delete a run — ?hard=&dry_run= |
| POST | /api/projects/{name}/runs/{run_id}/label |
Label a run |
| POST | /api/projects/{name}/vacuum |
Permanently remove soft-deleted runs |
| GET | /api/settings |
User settings |
| PUT | /api/settings |
Update user settings |
| GET | /api/projects/{name}/export |
Write a run to disk — ?run_id=&format= |
GET /api/projects takes ?all=1 to include archived projects.
DELETE takes its options from the query string, not a body — a body on a
DELETE is not universally supported by clients and proxies. Every destructive
route accepts dry_run=1 and returns the identical plan without changing
anything, so a confirmation dialog can show exactly what is about to happen.
Errors
| Status | When |
|---|---|
| 400 | Bad request, or an ArenaError — carries error (plain) and detail (raw) |
| 403 | Host not allowed |
| 404 | No such endpoint, project or job |
| 413 | Body too large |
| 500 | Unexpected — carries error and detail, and the server stays up |
Error bodies always have an error string suitable for showing a user; detail
carries the technical text when there is one.
Jobs
POST /api/projects/{name}/run starts a run in a thread and returns immediately.
Poll GET /api/jobs/{job_id}:
{
"id": "a1b2c3d4e5f6",
"project": "support_triage",
"status": "running",
"completed": 42,
"planned": 120,
"fraction": 0.35,
"elapsed_s": 18.4,
"eta_s": 34.2,
"skipped": {"opus_5": "ANTHROPIC_API_KEY is not set"},
"recent": [{"model": "sim_small", "test": "t1", "status": "ok", "passed": true, "output": "billing"}],
"run_id": null,
"result": null,
"cancel_requested": false
}
status is starting, running, done, error or cancelled. recent holds
the last 40 results. When status is done, result carries the full presented
leaderboard.
Starting a run for a project that already has one running returns the existing job rather than starting a second — double-clicking Run must not spend twice.
At most 50 finished jobs are retained; a running job is never evicted.
What-if
POST /api/projects/support_triage/whatif
{"run_id": "run_20260902_162437_9b554f",
"weights": {"accuracy": 0.7, "cost": 0.2, "latency": 0.1}}
Re-ranks from stored results — no model calls, no spend. It runs the real
build_leaderboard, so a what-if and a fresh run can never disagree.
Cross-site requests
A state-changing request (anything but GET) carrying an Origin from another
site is refused with 403. The Host allow-list stops DNS rebinding but not a
plain cross-site form POST, which carries a legitimate Host header — this closes
that. Same-origin requests either omit Origin or send a matching one, and the
CLI sends none.
Planned
Provider management over HTTP, cross-project run listing, and server-sent events replacing polling. See ../roadmap/status.md.
Source: docs/reference/http-api.md