Controller HTTP API

The controller and the logs service expose HTTP APIs under the /api/v1 base path. The CLI, runners, the dashboard, and pipelines' cross-run refs are all clients. Responses are JSON.

The complete route surface -- every method, path, and the scope each requires, for both services -- is generated from the routing code in api-reference.md. This page covers the cross-cutting behavior that table doesn't.

AuthenticationSection anchor link

Requests carry a bearer token, and each route declares the scope it needs; admin satisfies any check. Token kinds, the scope set, the unauthenticated endpoints, and first-visit admin bootstrap are in auth.md.

WebhooksSection anchor link

POST /webhooks/github/{pipeline} ingests GitHub deliveries. It is verified by HMAC (X-Hub-Signature-256) rather than a bearer token, since GitHub can't carry one; the handler acts on push and pull_request (opened/synchronize/reopened) and answers ping. A delivery naming a repository the pipeline is not bound to answers 404, re-sending a body the controller already accepted answers 409 with the run the first delivery produced, and a delivery with no X-GitHub-Delivery header answers 400. See security.md.

POST /api/v1/webhooks/github/bindings (scope admin) stores the secret one repository's deliveries to one pipeline are signed with and allows that repository for the pipeline; DELETE on the same path removes it. Stored bindings add to the GITHUB_WEBHOOK_BINDINGS document rather than replacing it. sparkwing cluster webhooks connect drives both sides; see hooks.md.

Logs serviceSection anchor link

Logs live in a separate service keyed by run and node (/api/v1/logs/{runID}/{nodeID}), with a whole-run read and an SSE stream for live tail. The routes and their scopes are in api-reference.md.

Run coordinationSection anchor link

A pipeline binary needs more than node state from whatever holds its runs: it dispatches its own child triggers, and it measures what the run cost so the next run of the same pipeline is priced from evidence. Those reach the controller as routes too -- /api/v1/runs/{id}/pending-triggers and /api/v1/triggers/{id}/claim for the child-trigger loop, /api/v1/pipelines/{name}/profile/observations, /contention, and /waits for the capacity profile, /api/v1/runs/{id}/nodes/{nodeID}/usage for a reaped process's accounting, and /api/v1/maintenance/reconcile-orphans for the sweep that closes runs whose orchestrator died.

A capacity write names a pipeline rather than a run, so it is bound to a live claim on a run of that pipeline: a node claim for a runner executing one node, or the run's trigger claim for the orchestrator, which records the queue wait before the first node exists and the run's measurement after the last one is gone.

ConcurrencySection anchor link

The .Memoize() and .Concurrency() coordination primitives are backed by the /api/v1/concurrency/{key}/* routes (acquire, heartbeat, release, state, resolve). See caching.md for the model.