HTTP API¶
The control plane (serve) exposes a REST API under /api, a Server-Sent Events stream at /api/sse, and the embedded web UI at /. All JSON endpoints return application/json.
Endpoint index¶
| Method | Path | Description |
|---|---|---|
POST |
/api/tasks |
Schedule a scan |
GET |
/api/tasks |
List tasks (?status=, ?show_seen=true) |
GET |
/api/tasks/{id} |
Get one task |
GET |
/api/tasks/{id}/result |
Get the analysis result for a completed task |
DELETE |
/api/tasks/{id} |
Delete a task |
POST |
/api/tasks/{id}/seen |
Mark a task seen |
POST |
/api/tasks/hide-all |
Hide all tasks |
GET |
/api/sse |
Server-Sent Events task status stream |
GET |
/api/workers |
List live workers |
POST |
/api/workers/{id}/pause |
Pause a worker |
POST |
/api/workers/{id}/unpause |
Unpause a worker |
GET |
/api/repos |
Latest successful analysis per repo |
GET |
/api/repos/{id} |
One repo's latest analysis |
GET |
/api/repo-config?url= |
Read per-repo overrides |
POST |
/api/repo-config |
Set per-repo overrides (partial upsert) |
DELETE |
/api/repo-config?url= |
Remove per-repo overrides |
GET |
/livez |
Liveness |
GET |
/readyz |
Readiness (Postgres + NATS) |
GET |
/health |
Alias of /livez |
GET |
/ |
Web UI (embedded single-page app) |
Tasks¶
POST /api/tasks — schedule a scan¶
Request body:
| Field | Required | Description |
|---|---|---|
repo_url |
yes | Git repository URL |
ref |
no | branch, tag, or commit SHA |
engine |
no | defaults to worker_engine |
Response 201 Created:
{
"id": "task-1786482521535742990",
"repo_url": "https://github.com/golang/example",
"ref": "HEAD",
"status": "queued",
"engine": "local",
"attempt": 0,
"max_attempts": 3,
"created_at": "2026-08-11T12:00:00Z",
"updated_at": "2026-08-11T12:00:00Z"
}
GET /api/tasks — list tasks¶
Query params: status (pending|queued|running|completed|failed|expired), show_seen=true to include tasks marked seen.
GET /api/tasks/{id} — get a task¶
Returns the full task object. Once the task reaches completed, it carries analysis_id.
GET /api/tasks/{id}/result — get the analysis¶
Returns the stored analysis. Returns 404 if no analysis exists yet and 409 if the task has not completed.
{
"id": "a3f2c8d1e9b04f7a",
"repo_id": "b7c9d2e1f3a45678",
"repo_info": {
"url": "https://github.com/golang/example",
"ref": "HEAD",
"commit_sha": "abc123",
"metadata": { "stars": 12345, "forks": 3456, "language": "Go", "license": "BSD-2-Clause" }
},
"summary": { "average": 42.5, "combined": 85.0, "exceeded": 3, "total_funcs": 24 },
"entries": [ ],
"engine": "local",
"crap_version": "v0.5.1",
"analyzed_at": "2026-08-11T12:00:05Z",
"duration_ms": 4500,
"raw_output": { "version": "report-v1", "summary": { }, "entries": [ ] }
}
DELETE /api/tasks/{id} — 204 No Content¶
POST /api/tasks/{id}/seen — 204 No Content¶
POST /api/tasks/hide-all — 204 No Content¶
Server-Sent Events¶
GET /api/sse opens a long-lived text/event-stream. Each status change is pushed as a task event:
event: task
data: {"id":"task-...","repo_url":"https://github.com/golang/example","ref":"HEAD","status":"running","attempt":0,"max_attempts":3,"created_at":"2026-08-11T12:00:00Z","updated_at":"2026-08-11T12:00:01Z","seen":false,"hidden":false}
event: task
data: {"id":"task-...","repo_url":"https://github.com/golang/example","ref":"HEAD","status":"completed","attempt":1,"max_attempts":3,"created_at":"2026-08-11T12:00:00Z","updated_at":"2026-08-11T12:00:05Z","seen":false,"hidden":false}
Workers¶
| Endpoint | Description |
|---|---|
GET /api/workers |
List workers with id, host, pid, cpu, and status |
POST /api/workers/{id}/pause |
Stop a worker from consuming (204) |
POST /api/workers/{id}/unpause |
Resume a worker (204) |
Pausing one worker does not affect the others. See worker.
Repos¶
| Endpoint | Description |
|---|---|
GET /api/repos |
Latest successful analysis per repo (excludes errors) |
GET /api/repos/{id} |
One repo's latest analysis by analysis ID |
Per-repo config¶
| Endpoint | Description |
|---|---|
GET /api/repo-config?url=<url> |
Read overrides |
POST /api/repo-config |
Partial upsert — only fields present in the body change |
DELETE /api/repo-config?url=<url> |
Remove overrides |
POST /api/repo-config body (all fields optional, url required):
{
"url": "https://github.com/org/repo",
"ref": "release",
"test_args": "-race ./pkg/...",
"crap_args": ["--exclude", "legacy/.*"],
"submodules": true,
"timeout": "30m",
"git_timeout": "30m",
"go_flags": "-p=1 -parallel=2"
}
Health & readiness¶
| Endpoint | Purpose |
|---|---|
GET /livez |
Liveness — process is up |
GET /readyz |
Readiness — Postgres reachable and NATS connected |
GET /health |
Alias of /livez (used by the Docker HEALTHCHECK) |
curl http://localhost:8080/livez
# {"status":"ok"}
curl http://localhost:8080/readyz
# 200 {"status":"ok"} or 503 {"status":"error","reason":"..."}
The Kubernetes manifests in infra/ probe /readyz (readiness) and /livez (liveness); the Docker image uses /health in its HEALTHCHECK.
Errors¶
Errors return a JSON object with a message and the appropriate HTTP status (400 for bad input, 404 for missing resources, 500 for internal errors).