Synced from Hive. This page is pulled from hivecommons/hive@v5 during the docs build. Edit the canonical source in the Hive repository.
Dashboard REST API reference — Packs and ACMM
Part of the Dashboard REST API reference; see that page for how auth levels and pkg/<file>:<line> source citations are read and checked. Edit this file for routes in this area and run src/scripts/check-api-reference-citations.sh --fix to resync citations.
| Method | Path | Auth | Purpose | Source |
|---|---|---|---|---|
GET | /api/packs | Dashboard auth/session | Packs List | pkg/dashboard/api.go:329 |
POST | /api/packs/{level}/apply | Owner only | Pack Apply | pkg/dashboard/api.go:330 |
PUT | /api/packs/level | Owner only | Set the ACMM level. Body {"level": N} persists and applies the pack; optional {"release_level_holds": true} performs a deliberate one-off release of Hive App level-applied hold labels for the transition. Level holds are otherwise never auto-released. When the change makes the L6 self-authored merge sweep eligible, the success body adds self_merge_sweep_active, level_changed_at, level_holds_pending: [{repo, number, title, url, agent}], and repos: [{repo, auto_merge}] so the dashboard can show the owner an informational notice and repo toggles. | pkg/dashboard/api.go:331 |
POST | /api/repos/rescan | Dashboard auth/session | Re-enumerate watched repositories’ open issues/PRs for the dashboard Projects cards. The scan is read-only with respect to agents/governor actions, collapses concurrent requests, and debounces repeated presses for 30 seconds (repoRescanDebounce) so the button cannot hammer GitHub | pkg/dashboard/api.go, pkg/dashboard/api_repos_rescan.go |
POST | /api/repos/pause | Owner | Quiet ONE repository without stopping the hive: agents stop writing to it and stop being handed work on it, while it keeps its dashboard card and ACMM eval. Body {"repo": "...", "reason": "..."} — the repo travels in the body because a project.repos entry may be an explicit cross-org owner/name. Records who/when/why; changed: false marks a no-op re-pause and leaves the original provenance intact; persisted: false means the pause is in force but could not be written to config. Rejects a repo outside project.repos. See per-repo agent pause | pkg/dashboard/api_repo_pause.go |
POST | /api/repos/resume | Owner | Lift a repository’s pause. Unlike pause, does not require the repo to be in project.repos, so a pause left behind by a removed repo can still be cleared | pkg/dashboard/api_repo_pause.go |
GET | /api/repos/pauses | Dashboard auth/session | Every repo pause with its provenance. Read-only — the same state is already on the repository cards | pkg/dashboard/api_repo_pause.go |
| GET | /api/repos/{owner}/{repo}/hold-permission | Dashboard auth/session | Reports whether the signed-in user can toggle hold chips for a repository card. Owners are allowed; otherwise the server checks GitHub repos/{owner}/{repo}/collaborators/{user}/permission and briefly caches the effective permission | pkg/dashboard/api.go:346, pkg/dashboard/api_repo_hold.go |
| GET | /api/repos/{owner}/{repo}/items/{number} | Dashboard session | Per-item detail for the activity monitor’s decision badge: {repo, number, stage, stage_slug, operator_actions[], operator_action_count, last_operator_action}. operator_actions[] is the server-side ledger of every row action submitted from the dashboard (hold/release, decision, decide-delegated, send-to-human, accept-triage — recorded by recordOperatorAction around those routes) as {action, actor, at, note, target, outcome: pending|applied|no-op|failed, detail, head_sha, stage}; last_operator_action adds the derived state (waiting|addressed|stuck|pending|failed), stuck_after_seconds and followed[] (the stage hops after the action). The same last_operator_action / operator_action_count ride every stage-tracker row and linked PR. Persisted in /data/operator-actions.json; HIVE_OPERATOR_ACTION_STUCK_AFTER (default 30m) sets the stuck window. Rows also carry rebase_merge: {state: approved|rebasing|ci-pending|merged|failed, actor, base, text, fail_reason, conflict_files[], armed_at} while a Rebase, then merge as maintainer follow-through is live or just settled (persisted in /data/rebase-merge-followups.json; fail_reason: foreign-head when the head moved after the approval by someone other than the hive, so nothing merged), and rebase: {attempt, max, exhausted, head, text} on a conflicting PR the fixer is rebasing under the two-attempt loop guard (/data/rebase-attempts.json). | pkg/dashboard/api.go, pkg/dashboard/operator_actions.go |
| POST | /api/repos/{owner}/{repo}/items/{number}/hold | Owner or repo write/maintain/admin | Add or remove a dashboard hold chip for an issue or PR. Body {"held": true} adds the hive’s exact-match canonical hive-pause/<hive-id> label (shortened with a hash suffix when the plain spelling would exceed GitHub’s 50-character label cap, #17037); {"held": false} removes exactly the labels causing the hold (hive-pause/<hive-id> and/or generic hold labels) and never removes hive/<hive-id> provenance. A GitHub failure answers 424 with the step and GitHub’s reason — never 502/503, which the hosted Ingress replaces with its generic “hive unavailable” page. Every toggle writes an audit-log entry; the #8851 upgrade migration writes /data/hive-hold-migration-<hive-id>.json with audit-backed, provenance-only, and ambiguous legacy hive/<hive-id> classifications. | pkg/dashboard/api.go:350, pkg/dashboard/api_repo_hold.go |
| POST | /api/repos/{owner}/{repo}/items/{number}/decision | Owner or repo write/maintain/admin, signed-in GitHub user | Record an operator decision from the activity monitor’s row menu / selection bar. Body {"decision": "approve"|"direction"|"close-superseded"|"close-wontfix"|"close-read"|"close-issue"|"adopt"|"reroute"|"re-author"|"merge-now"|"queue-merge", "comment": "…", "type": "issue"|"pr", "superseded_by": "#458", "force": true, "to": "<login>"|"any"} (force only with adopt, to only with reroute; an unknown decision is a 400). On an issue parked at 045, approve/direction act exactly as a maintainer’s /hive approve / /hive decision <text> comment would (labels, approved-direction, stage-engine Unparked fact, reply comment naming the operator). On a PR parked at 085 (type: "pr"), approve posts the /lgtm and /approve commands as two comments attributed to the operator, records the approval against the head as the human answer to a requires_human / decision-requested verdict (the lanes and the stage engine honour it like a trusted merger’s Approve review), lifts needs-human, clears the sentinel label and acknowledges the bundle’s sentinel findings for that head, re-derives the stage and drops the tracker cache — the response adds released (approval-recorded, needs-human, sentinel-label, sentinel-acknowledged) and stage; the merge gate decides from there (no review as the operator, no merge) — and direction records the text as a human change request against the head — the /hive decision fix lane — on a hive-authored PR, and on a contributor’s clanker PR (author/clanker / hive attribution markers) where the text is posted to the contributor’s hive and the PR moves to stage/072-returned-to-contributor (status: "direction-returned-to-contributor"); a person’s PR, which no hive can carry direction to, answers 409. The close variants first post a required comment naming the operator (and the superseding PR when superseded_by is given); a failed comment prevents closure. close-superseded and close-wontfix close as not planned; close-read acknowledges an advisory issue and closes it as completed. close-issue (type: "issue" only) closes the single named open issue as completed, at any stage, leaving linked PRs and other issues untouched; it returns 400 for a PR (including one sent as an issue), 404 when the issue is not found, and 409 when already closed. It retains the existing permission and mutation gates, including the paused-repo status: "refused" result without writes. adopt and re-author require type: "pr" and queue adoption via the adoption/supersede label (409 unless governor.labels.stage.adoption is enabled). adopt with "force": true is Adopt now (#15038): it bypasses the author’s presence, the grace window and maintainer_policy, marks the stage record and nudges the automerge sweep so the adoption engine supersedes the head at once (responds status: "already-adopted" when nothing is left to do; 400 on the hive’s own PR). reroute (type: "pr", "to": "<clanker login>"|"any") hands the PR’s follow-up work to another online clanker through the contributor fix queue, posts a note and arms a pickup timer that falls back to Adopt now after `governor.reroute.pickup_timeout`; answers `status: "rerouted"`, 409 when no clanker is online or adoption is disabled. On a PR at 090, `queue-merge` (*Approve and let hive merge*, the lead action) records an approval attributed to the operator and applies the automerge label, returning `status: "queued-for-merge"` (on the hive's own PR, which the App cannot review, the queue note is a comment and the operator's approval is recorded as `approve` records it — `released` lists `approval-recorded`, `admitted`); hive's merge sweep merges on its next tick once CI is green and the head has soaked, and that recorded operator approval is the one thing that lets the sweep merge an untrusted-author PR (the merge comment names the operator). `merge-now` (*Merge now as maintainer*, secondary) posts an attributed comment and merges immediately through the hive's GitHub App, returning `status: "merged"`. Both require `type: "pr"` and a verified owner or `merger` role in addition to the route's repo permission check; both refuse closed/merged PRs and the operator's own PR. `merge-now` on a PR whose `mergeable_state` is `dirty`/`behind` instead answers `202` with `status: "rebase-queued"` (approval recorded, rebase kicked, merge follows via the follow-through once the hive's own rebase is green; a head pushed by anyone else fails the follow-through closed; `head-not-pushable` refusal on a fork without maintainer edits). `approve` on such a PR adds `rebase: {attempt, max, exhausted, head, text}` for the rebase episode it opened. Every decision writes an audit-log entry (`repo_item_decision_*`, e.g. `repo_item_decision_adopt_now`, `repo_item_decision_reroute`). | `pkg/dashboard/api.go:351`, `pkg/dashboard/api_repo_item_decision.go` | | `GET` | `/api/notes` | Signed-in GitHub user (or verified owner) | Maintainer notes — the private per-item notes maintainers leave each other on Activity Monitor rows ([#maintainer-notes](https://github.com/hivecommons/hive/blob/v5/src/docs/operator-reference.md#maintainer-notes)). `?repo=owner/name&number=N` lists one item's live notes oldest first; with no query, every live note in the hive (the SPA's count badges). `{notes: [{id, repo, number, author, text, created_at}]}`. Never read from or written to GitHub. | `pkg/dashboard/api.go:355`, `pkg/dashboard/api_maintainer_notes.go` | | `POST` | `/api/notes` | Owner or repo write/maintain/admin, signed-in GitHub user | Append a maintainer note. Body `{repo, number, text}` — `text` is plain text up to 2000 characters; the author is the dashboard session's GitHub login, never a body field. `401` signed out, `403` without write on the repo, `400` for an empty/oversized note or a bad item. `201 {ok, note}`; appended to `/data/maintainer-notes.jsonl`, audited as `maintainer_note_add`, and broadcast as the `maintainer-notes` SSE event `{seq, repo, number, count, action}` so other maintainers' browsers refresh the row. | `pkg/dashboard/api.go:356`, `pkg/dashboard/api_maintainer_notes.go` | | `DELETE` | `/api/notes/{id}` | The note's author, or a verified owner | Tombstone a maintainer note (append-only store: a `deleted_at` record, never a rewrite). `404` unknown or already deleted, `403` for anyone else. `{ok, note}`; audited as `maintainer_note_delete`, broadcast as `maintainer-notes`. | `pkg/dashboard/api.go:357`, `pkg/dashboard/api_maintainer_notes.go` | | `POST` / `GET` | `/api/repos/{owner}/{repo}/items/{number}/decide-delegated` | Same as `…/hold` (owner or repo write), signed-in GitHub user; `decisions.delegate.enabled` (403 when off) | *Let hive decide*: enqueue a delegated-decision job in which the hive decides the parked item itself as the owner's delegate under `pkg/delegatedecision.PolicyText` and records it as a human decision (`/hive decision` comment with the marker, park labels off, triage/accepted, queue approval on a PR head). Body {"type": "issue"|"pr", "reason", "caption", "sentinel_reason"}. POST answers 202 with the job ({id, status: "running"|"done"|"failed", decision: {verdict, rule, text, rationale, actions, keeps_park, ladder?: {decision, reason, ruled_out[]}}, notes, error}; ladder is set on contributor-PR decisions and names the rung that decided — already-fixed → mergeable-as-is → rebase-then-merge → return-to-contributor → contributor-offline — and why each rung above it did not apply); 409 with the running job while one is in flight; GET reads it. Rails: never merges, refuses items wearing hold, never grants secrets/permissions (needs-owner + hold instead), recommends only on hive policy/governance questions (keeps 085). Audited as repo_item_decision_delegated[_failed]. See dashboard-sections.md. | pkg/dashboard/api.go:329, pkg/dashboard/api_repo_item_decide_delegated.go |
| POST | /api/repos/{owner}/{repo}/items/{number}/decide-delegated/plan | Same as …/decide-delegated | Preview for Let hive decide: runs the same owner-delegate policy with no side effects (nothing written, no job) and answers {would, rule, acting, title, summary, why, consequences[], ladder, mergeable_as_is, alternatives[], writes: false, prior, notes} — summary is one verb-first sentence with no “Hive would:” prefix (Send #15332 back to its author with one instruction: make CI green, nothing else.), why one short line (CI is red.), consequences at most two items, ladder the rung that decided with its ruled_out list, and mergeable_as_is whether an as-is merge is honest (the queue-merge and merge-now alternatives are dropped when it is not). The dialog shows Cancel / Let hive do that, plus Approve and let hive merge (or Merge now as maintainer when the hive would approve anyway) only when mergeable_as_is; only then does the SPA post …/decide-delegated with chosen naming the button. A transient GitHub failure (5xx, rate limit, timeout) is retried twice with backoff before the plan gives up. A failure answers {ok: false, error, reason, retryable, attempts, elapsed_ms, hive, hint} under 409 when the item is no longer open (reason: not-open) or 424 when a read it depends on failed (github), is not configured (config) or the plan itself failed (plan) — never 502/503, which the hosted Ingress intercepts and replaces with its generic hive unavailable page (hivecommons/hive#16776). See dashboard-sections.md. | pkg/dashboard/api.go:360, pkg/dashboard/api_repo_item_decide_delegated_plan.go |
| POST | /api/repos/{owner}/{repo}/items/{number}/blocker/recheck | Same as …/hold (owner or repo write), signed-in GitHub user | Re-check permissions on an issue parked on a capability blocker (#15099): probes the GitHub App installation’s permissions; when the missing permission is granted, clears every ledger entry blocked on it and un-parks the item ({result: {outcome: "released", …}}), otherwise {result: {outcome: "no-effect", reason: "blocker", options: [...]}}; 404 when the item has no blocker, 409 when the blocker is not a capability gap | pkg/dashboard/api.go:361 |
| POST | /api/repos/{owner}/{repo}/items/{number}/blocker/close-partial | Same as …/hold (owner or repo write), signed-in GitHub user | Option (c) on a capability blocker (#15099): labels the issue hive/partially-addressed, posts the operator’s {"comment"} naming what remains and closes it; the ledger entry is cleared | pkg/dashboard/api.go:362 |
| POST | /api/repos/{owner}/{repo}/items/{number}/accept-triage | Owner or repo write/maintain/admin | Accept triage on an issue from the activity monitor’s row menu — what a maintainer accepting triage on GitHub does by hand: adds the reporter-trust triage label (triage/accepted, or the first of issue_filter.reporter_trust.untrusted_require_labels; the label is created in the repo if missing) and removes the awaiting label (needs-triage / reporter_trust.awaiting_label) and a Hive-written stage/015-triage-needed. Empty body. Returns {ok, repo, number, acceptLabel, added, removed, alreadyAccepted, minStatusSeq}; an issue already carrying the accept label with nothing left to remove is a no-op (alreadyAccepted: true, no write, no audit). 404 for a pull request or an unknown item. The status snapshot’s labels are patched so the next reconcile reads 010 Actionable before the engine’s own 015 → 010 derivation. Every write is audited as repo_item_accept_triage under the dashboard user. | pkg/dashboard/api.go:561, pkg/dashboard/api_repo_item_accept_triage.go |
| POST | /api/repos/{owner}/{repo}/items/{number}/sentinel-confirm | Owner or repo write/maintain/admin | Confirm safe on the activity monitor’s human-gate pill (#17470): a person confirms a sentinel finding is intended. Body {comment?, type?} (type is pr or issue, comment ≤ 4000 characters). Posts the confirmation as a comment first — <!-- hive:sentinel-confirmed -->, @user confirmed the sentinel finding safe from the dashboard., then the comment text — and removes every label matching the configured sentinel label (sentinel.label, default sentinel-alert, case-insensitive). Returns {ok, repo, number, label, removed, alreadyClear, result}; with no sentinel label on the item the comment is still posted and alreadyClear is true. 403 for a repository the hive does not watch. Audited as repo_item_sentinel_confirm under the dashboard user; the operator-action ledger records it as confirm-sentinel. | pkg/dashboard/api.go:352, pkg/dashboard/api_repo_item_sentinel_confirm.go |
| GET | /api/acmm/evaluation | Dashboard auth/session | ACMMEvaluation — combined codebase + operational result, cached server-side for 1 hour (acmmEvalTTL). ?refresh=1 (the dashboard’s ”🔄 Re-evaluate” button, #5877) bypasses the hourly TTL but is debounced server-side: requests within 1 minute of the last evaluation (acmmRefreshDebounce) still serve the cache, since a full refresh costs up to ~29 GitHub GetContents calls per repo. The response’s last_evaluated_at timestamp reports when the cached evaluation was computed | pkg/dashboard/api.go:364, pkg/dashboard/api_acmm_eval.go |
| POST | /api/acmm/issue | Owner only | ACMMCreate Issue — files on GitHub or, with governor.acmm.issue_tracker: work_source / body tracker: "work_source" on a Linear-sourced hive, on Linear; response tracker says which. See ACMM policy matrix | pkg/dashboard/api.go:365 |
| GET | /api/acmm-recommendation | Dashboard auth/session | Advisory level-up recommendation (acmmadvisor.Recommendation, JSON): never changes the applied level — see ACMM advisor | pkg/dashboard/api.go:366 |
| GET | /api/hive-advice | Dashboard auth/session | Weekly owner advice (hiveadvisor.Result, JSON) shared with the advisory digest: recommendations with items, links (Overview export paths) and bands; bands[] rule text; counts naming the governor actionable queue vs. the Overview total. Which recommendations show is frozen for 7 days; their numbers refresh every call — see Owner advice | pkg/dashboard/api.go:368, pkg/dashboard/api_hive_advice.go |