Public API and CLI
Drive Swarm programmatically with the versioned /api/v1 surface, the remote CLI, and signed outbound webhooks.
After this page you can start and inspect bees from a script or CI job, use the remote CLI instead of raw HTTP, and subscribe an external system to run-lifecycle events. The contract holds for programmatic callers too: No human writes code. A human always reviews.
The v1 API
The dashboard serves a small, stable API at /api/v1 (ADR 0091). It is separate from the dashboard's internal routes, which are unversioned and may change without notice. The endpoints:
| Endpoint | What it does |
|---|---|
POST /api/v1/runs | Dispatch a bee |
GET /api/v1/jobs | List runs (filters + pagination) |
GET /api/v1/jobs/{id} | One run's detail |
GET /api/v1/jobs/{id}/events | The run's pipeline events (JSON snapshot, not a stream) |
POST/GET/DELETE /api/v1/webhooks | Manage outbound-webhook subscriptions |
GET /api/v1/skills | Live dashboard-authored skills (consumed by the swarm agent at boot) |
GET /api/v1/openapi.json | The OpenAPI 3.1 contract (public, no auth) |
Auth. Send X-API-Key with a key an operator has issued you (named keys come from the DASHBOARD_API_KEYS_JSON map), or call from a logged-in 3SC SSO session. Keys are compared constant-time and never logged; only the key's name is.
curl -s -H "X-API-Key: $SWARM_API_KEY" https://<dashboard-host>/api/v1/jobs | jq .
curl -s -X POST -H "X-API-Key: $SWARM_API_KEY" -H "Content-Type: application/json" \
-d '{"recipe":"support-bee","repo":"3sidedcube/some-api","ticket":"Fix the 500 on GET /api/health"}' \
https://<dashboard-host>/api/v1/runsWindow-driven recipes (like cleaner-bee) return 200 {skipped: true} when the repo HEAD hasn't changed since the last successful run; pass "force": true to run anyway.
Rate limits. Every response carries RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset; going over gets a 429 with Retry-After. Defaults: 120 rpm authenticated, 30 rpm on public endpoints. The limiter is in-process per ECS task, so treat it as abuse protection, not quota.
The remote CLI
cli/ in the repo is a dependency-free client for the same API (ADR 0092); run it from a laptop or CI with Node 20+. Auth comes from SWARM_API_URL + SWARM_API_KEY env vars, or a ~/.swarm/credentials JSON file that must be mode 0600. The key is never accepted as a flag, because flags land in shell history.
export SWARM_API_URL="https://<dashboard-host>/api/v1"
export SWARM_API_KEY="<your-key>"
node cli/bin/swarm.js run --recipe tinfoil-bee --repo org/repo --jira ABC-123
node cli/bin/swarm.js list --status running --limit 20
node cli/bin/swarm.js get <pipelineId>
node cli/bin/swarm.js tail <jobId>Output is human-readable by default; add --json for CI. --verbose redacts the API key and any signing secret.
Don't confuse this with scripts/run-agent.sh, which runs a bee locally on your own machine (clone, recipe, PR) rather than asking the deployed Swarm to do it. See other triggers.
Signed outbound webhooks
External systems can receive an HMAC-signed POST on run.started (dispatch-accept) and run.finished (terminal settlement: success, verdict-without-PR, or failure).
node cli/bin/swarm.js webhooks add --url https://ci.example.com/hook --events run.started,run.finished
node cli/bin/swarm.js webhooks list
node cli/bin/swarm.js webhooks remove --id <subscriptionId>webhooks add prints the signing secret exactly once, so store it immediately; it can't be retrieved later. The target URL must be HTTPS, and internal/private addresses are rejected.
Each delivery carries X-Hub-Signature: sha256=<hmac> (HMAC-SHA256 over the exact raw body, keyed by your secret), plus X-Swarm-Event and X-Swarm-Delivery headers. The payload is { event, pipelineId, jobId, recipe, repo, ticketKey, status, prUrl, ts }: no raw ticket text, no secrets. Verify with a constant-time compare:
const computed = "sha256=" + crypto.createHmac("sha256", SECRET).update(rawBody).digest("hex");Delivery is best-effort: one attempt, no retry, and a failed delivery never affects the run. Each attempt shows up as a webhook_delivery event on the run's timeline in the dashboard.