Skip to content

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:

{
  "repo_url": "https://github.com/golang/example",
  "ref": "HEAD",
  "engine": "local"
}
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}/seen204 No Content

POST /api/tasks/hide-all204 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}
curl -N http://localhost:8080/api/sse

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).