Kanon
Reference

API reference

All API endpoints require authentication via either a browser session or a Bearer token (KANON_API_TOKEN).

Discovery

POST /api/discover

Run the taxonomy discovery pipeline.

Body:

{
  "repoSlug": "your-org/your-app",
  "localPath": "/path/to/repo",     // optional if GitHub source configured
  "shaped": true,                     // optional: conform to approved structure
  "crawl": false                      // optional: also crawl the running app
}

Response: { runId, domains, features, refreshed, signals }

GET /api/taxonomy?repoSlug=…

Fetch the taxonomy state (approved and proposed nodes).

Response: { exists, approved, proposed, spaces }

POST /api/taxonomy

Bulk-approve proposed nodes.

Body: { repoSlug, nodeIds? } — omit nodeIds to approve all.

Response: { approved, total, errors? }

Scanning

POST /api/scan

Deep-scan one feature.

Body:

{
  "repoSlug": "your-org/your-app",
  "localPath": "/path/to/repo",     // optional
  "featureId": "your-org/your-app::domain::feature",
  "guide": true,                     // optional: generate the full guide
  "targetCommitSha": "abc123..."    // optional: pin the run to a commit
}

Response: { runId, status }

POST /api/scan-all

Scan all approved features with bounded parallelism.

Body:

{
  "repoSlug": "your-org/your-app",
  "fleetConcurrency": 3,            // optional
  "guide": true                      // optional
}

Response (202): { accepted, features, skipped }

GET /api/scan-all?repoSlug=…

Poll fleet status.

Ingestion

POST /api/ingest

Upload a discovery bundle (from the plugin).

Body: { repoSlug?, bundle } (max 25MB)

Response: { runId, crawlRunId, domains, features, refreshed, reviewUrl }

GET /api/ingest?repoSlug=…

Recent ingest runs.

Changes

GET /api/changes?repoSlug=…&status=open,implementing&linearId=ENG-42&limit=10

List spec changes.

Response: { changes: [{ id, title, summary, status, direction, source, linearIdentifier, linearUrl, featureId, claims }] }

POST /api/changes

Update a change's status.

Body: { repoSlug, changeId, status: "implementing" | "resolved" | "dismissed" }

Worker

The worker daemon's queue endpoints. Machine-token only, and the token must be workspace-scoped (from /kanon:setup) — browser sessions and the legacy instance-wide env token are refused. Normally only the daemon calls these; see The worker daemon.

POST /api/worker/claim

One poll: heartbeat the daemon's presence, expire stale leases, atomically claim at most one queued task.

Body: { workerId, hostname?, version?, repoSlugs: [string] }

Response: { task: { id, repoSlug, type, payload, status, attempt, cancelRequested, specChangeId, leaseExpiresAt, createdAt } | null, retryAfterSeconds, leaseSeconds, deniedRepoSlugs }

type is work_change, discover, or scan; the payload shape follows it — {changeId, branch, sliceN?, planOnly?}, {mode: "code_only"}, or {feature?}.

POST /api/worker/tasks/{taskId}/events

Heartbeat + activity batch (an empty batch is a pure heartbeat). Re-sending a batch is safe — (taskId, seq) deduplicates. 409 means the lease was lost: stop the child process and stop reporting.

Body: { repoSlug, workerId, activities: [{ seq, kind: "status" | "thought" | "tool" | "text" | "error", label, detail? }] }

Response: { cancelRequested, leaseExpiresAt }

POST /api/worker/tasks/{taskId}/complete

The lease holder's terminal report. On success with a PR, the URL is written through to the change's prUrl/prNumber.

Body: { repoSlug, workerId, outcome: "succeeded" | "failed" | "cancelled", result: { prUrl?, prNumber?, branch?, summary?, errorMessage? } }

GitHub

GitHub is connected through an OAuth round trip in the browser, not through a machine API. These are session-only — a bearer token cannot use them.

GET /api/github/oauth/start

Redirects to GitHub to authorize. Sets an httpOnly state nonce that the callback compares, so a crafted callback cannot bind someone else's account.

GET /api/github/oauth/callback

GitHub returns here. Exchanges the code and stores the token encrypted at rest.

GET /api/github/repos

The repository picker's list for the connected account.

Authentication

POST /api/device/start

Start device authorization (for the plugin).

Response: { userCode, deviceCode, verifyUrl, expiresInSeconds, pollIntervalSeconds }

GET /api/device/poll?deviceCode=…

Poll for device approval. Unauthenticated: possession of the 256-bit device code IS the credential. Always answers 200 with a status body; unknown, consumed and raced codes all read as expired, so the endpoint cannot be used to probe which codes exist. The approved response carries the raw token exactly once.

Events & Tickets

POST /api/events/create-tickets

Create Linear tickets for uncovered analytics events.

Body:

{
  "repoId": "...",
  "routes": [{ "method": "POST", "route": "/api/foo", "featureName": "..." }],
  "mode": "batch"  // or "individual"
}

POST /api/chat/create-ticket

Create a Linear ticket from a chat draft.

Body: { repoId, threadId, messageId, widgetId, title, description }

MCP tools (plugin)

ToolDescription
kanon_setup_beginStart device auth
kanon_setup_pollPoll for device approval
kanon_whoamiCheck auth status
kanon_validate_bundleValidate a bundle file
kanon_assemble_bundleAssemble a discovery bundle
kanon_push_bundleUpload a bundle
kanon_get_taxonomyFetch taxonomy state
kanon_ingest_statusRecent ingest runs
kanon_approve_allBulk-approve proposals
kanon_list_changesList pending spec changes
kanon_update_changeUpdate change status
kanon_select_filesSelect closure files for a feature
kanon_collect_factsCollect mechanical facts
kanon_assemble_guideBuild guide from collected data
kanon_push_guideUpload a guide
kanon_get_guide_statusCheck guide freshness

On this page