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
POST /api/repos Register a repo (worker discovers its Go projects)
POST /api/repos/analyze Schedule analysis (all projects or project)
GET /api/repos/status?url= Registration status + discovered projects
GET /api/repos Latest successful analysis per repo/project
GET /api/repos/{id} One analysis by ID
GET /api/repo-config?url=[&subdir=] Read per-project overrides
POST /api/repo-config Set per-project overrides (partial upsert)
DELETE /api/repo-config?url=[&subdir=] Remove per-project 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 (legacy)

Kept for existing clients and the embedded UI. New clients should use POST /api/repos/analyze. It requires a registered repo (see POST /api/repos) and returns the first scheduled task only.

Request body:

{
  "repo_url": "https://github.com/golang/example",
  "ref": "HEAD",
  "project": "tsc",
  "engine": "local"
}
Field Required Description
repo_url yes Git repository URL (must be registered)
ref no branch, tag, or commit SHA — persisted to the repo's root config
project no analyze only this project; omitted = all discovered projects
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
POST /api/repos Register a repo — a worker clones it and discovers its Go projects
POST /api/repos/analyze Schedule analysis (all projects, or one via project)
GET /api/repos/status?url=<url> Registration status + discovered projects
GET /api/repos Latest successful analysis per repo/project (excludes errors)
GET /api/repos/{id} One analysis by ID

POST /api/repos — register a repo

Request body: { "repo_url": "https://github.com/org/repo" }.

Response 202 Accepted: the repo record with Status: "registering". A worker clones the repo, discovers its Go projects, and updates the record to Status: "ready" (or "error" with StatusReason). Poll GET /api/repos/status until the status is no longer registering.

POST /api/repos/analyze — schedule analysis

Request body (repo_url required):

{ "repo_url": "https://github.com/org/monorepo", "project": "tsc" }
Field Required Description
repo_url yes A registered repo URL
project no Analyze only this project (subdirectory); omitted = one task per discovered project

Response 202 Accepted: the queued task(s).

GET /api/repos/status?url=<url> — registration status

Response: the repo record — Status (registering | ready | error), StatusReason, CommitSHA, ProfiledAt, and Profile.projects (discovered Go projects). 404 if the repo was never registered.

Per-project config

Endpoint Description
GET /api/repo-config?url=<url>[&subdir=<project>] Read overrides
POST /api/repo-config Partial upsert — only fields present in the body change
DELETE /api/repo-config?url=<url>[&subdir=<project>] Remove overrides

subdir scopes an override to one project of a multi-module repo; omit it (or send empty) for the repo root. A project's ref falls back to the repo-root config's ref when the project has none.

POST /api/repo-config body (all fields optional, url required):

{
  "url": "https://github.com/org/repo",
  "subdir": "tsc",
  "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).