An ornate brass speaking-tube switchboard mounted floor-to-ceiling on the stone wall of the Plan Forge shop with dozens of labeled brass plug-ports arranged in a grid, glowing amber patch cords stretched between ports in patterns, a clerk-smith in a leather apron routing connections
Appendix W

REST API Reference

Every REST endpoint the Plan Forge MCP server exposes, grouped by subsystem, with verb, path, request body shape, response shape, and status codes. The companion to Appendix V — Event Catalog (which covers the WebSocket side) and Appendix Q — API Surface Index (which catalogs the MCP tool surface).

One server, three surfaces. The MCP server in pforge-mcp/server.mjs serves three concurrent surfaces on the same process: stdio MCP for IDE agents, REST + WebSocket on port 3100 for the dashboard and any external integration, and a Forge-Master HTTP surface for the conversational entrypoint. This appendix covers the REST + Forge-Master surfaces; the MCP tool surface is documented in Appendix Q.

Orientation

Plan Forge exposes ~91 REST endpoints across 16 subsystems. Every one of the 106 MCP tools can also be invoked over REST through the generic dispatcher (POST /api/tool/:name), the explicit endpoints below are the "first-class" surfaces the dashboard and CLI use, with response shapes shaped for direct UI consumption rather than tool-call envelopes.

SubsystemCountWhat it covers
Discovery4Liveness, version, capability manifest, well-known endpoint.
Plan execution & runs10List/trigger/abort plan runs, traces, replay, plans, workers.
Cost1Token-spend report across providers and months.
Search, timeline, hub3Cross-surface search, unified timeline, WebSocket upgrade.
Memory (L1/L2/L3)7Capture, drain, search, presets, OpenBrain stats and replay.
Crucible10Idea smelt lifecycle (submit, ask, preview, finalize, abandon, governance).
LiveGuard14Drift, incidents, deploy journal, regression guard, runbooks, hotspots, triage, secret scan, dep watch, env diff.
Quorum & fix proposals4Read/write quorum prompts, list/propose fix plans.
Tempering & bugs3Tempering artifact, bug stub from finding, bug list.
Skills (decision tray)5Pending decisions, accept/reject/defer, full skill catalog.
Inner loop7Reviewer calibration, gate suggestions, cost anomalies, proposed fixes, federation.
Bridge & approvals3Pending approvals, programmatic + browser-link approval.
Copilot integration5copilot-instructions.md read/preview/sync, OpenClaw snapshot/config.
GitHub & team coordination4GitHub metrics, readiness, team dashboard, team activity.
Notifications, audit, dashboard, settings13Notification config, audit config/drain, dashboard state, config, secrets, extensions, update, server restart.
Generic MCP dispatcher3The POST /api/tool/:name escape hatch that exposes any of the 106 MCP tools over REST.
Forge-Master10The conversational entrypoint, chat sessions, prompts, prefs, cache stats.
Image generation1Generate images via xAI Grok Aurora or OpenAI DALL-E.

Authentication, binding, and CORS

The trust model is local user. The server binds explicitly to 127.0.0.1 (loopback only) and runs no authentication layer of its own, the operating system's user account is the access boundary. Concretely:

  • Binding: app.listen(HTTP_PORT, "127.0.0.1", ...), remote hosts cannot reach the API; only processes running as the same OS user can.
  • Port: 3100 by default; override with PLAN_FORGE_HTTP_PORT (see Appendix U — Server Ports).
  • CORS: not enabled. Browser dashboards served from http://127.0.0.1:3100/dashboard share the same origin.
  • Bridge approvals (the only endpoint family that crosses a trust boundary) are protected by the per-run HMAC token in PFORGE_BRIDGE_SECRET, see Bridge & approvals.

If you need to expose the API beyond loopback (rare; usually it's the wrong solution), put a reverse proxy in front of it that handles TLS and authentication. Do not change the bind address; it's a deliberate safety boundary.

Body format. All POST/PUT endpoints expect Content-Type: application/json. The server uses express.json() with default 100 KB body limit; payloads larger than that return 413 Payload Too Large.

Error response shape

Endpoint handlers wrap exceptions in a consistent envelope:

// 4xx / 5xx
{
  "error": "Human-readable message",
  "code": "OPTIONAL_MACHINE_CODE",   // e.g. ASK_QUESTION_MISMATCH, PLAN_ALREADY_EXISTS
  "details": { ... }                  // optional structured context
}

Status codes follow standard HTTP semantics: 400 for malformed input, 404 for missing resources, 409 for state conflicts (most common in Crucible finalize), 413 for body limits, 500 for unexpected server errors. The complete error-code table lives in the Errors & Exit Codes appendix (forthcoming Appendix X).

Discovery

Lightweight endpoints intended for liveness checks, build identification, and capability negotiation. These are safe to poll, none of them allocate workers or write files.

MethodPathPurposeResponse
GET/.well-known/plan-forge.jsonPublic discovery manifest{ version, capabilities, dashboard }
GET/api/capabilitiesFull capability catalog (mirrors forge_capabilities){ tools[], workflows[], config, memory }
GET/api/versionRunning server version{ version, framework, build }
GET/api/statusLiveness + last error{ ok, lastError, uptimeMs }

Plan execution and runs

The lifecycle surface for pforge run-plan. Triggering a run returns immediately with a run ID; subscribe to the lifecycle event family over the WebSocket hub for progress.

MethodPathPurposeRequest / response notes
GET/api/runsList recent runs (last 50)Returns { runs: [{ id, plan, status, startedAt, endedAt }] }.
GET/api/runs/latestLatest run with full statusIncludes current slice, gate result, cost so far.
GET/api/runs/:runIdxSpecific run by indexrunIdx matches .forge/runs/<idx>.jsonl.
POST/api/runs/triggerKick off a plan runBody: { plan, mode, quorum, assisted, dryRun, escalate }. Returns { runIdx, pid }.
POST/api/runs/abortAbort the active runBody: { runIdx? } (defaults to current). Sends SIGTERM, then SIGKILL after grace.
GET/api/replay/:runIdx/:sliceIdSession replay log for a sliceReturns the journaled stdout/stderr stream for one slice, used by the dashboard's session-replay view.
GET/api/plansEnumerate hardened plansWalks docs/plans/ and parses Scope Contract headers.
GET/api/workersActive worker processesPIDs, model, slice, elapsed.
GET/api/tracesList execution traces (run index)Top-level summary: run, slice count, gate pass/fail.
GET/api/traces/:runIdTrace detail for one runPer-slice timing, model, tokens in/out, cost.

Cost

Plan Forge tracks token spend per provider, per model, per run, aggregated monthly. The single REST endpoint mirrors forge_cost_report; richer estimation lives in MCP tools (see Generic MCP dispatcher).

MethodPathPurposeResponse
GET/api/costCost report (token spend per model + monthly aggregation){ thisMonth, lastMonth, perModel: {...}, perRun: [...] }

Estimation endpoints (forge_estimate_quorum, forge_estimate_slice) are MCP-only; invoke via POST /api/tool/<name>.

Search, timeline, hub

Cross-surface search and the unified timeline are the dashboard's primary navigation aids. The hub endpoint is where browsers (and any other client) upgrade to a WebSocket to receive live events, see Appendix V — Consuming the Stream for a Node example.

MethodPathPurposeNotes
GET/api/searchCross-surface search (plans, events, bugs, incidents, memory)Query string: ?query=&source=&limit=. Returns { hits: [{ source, recordRef, snippet, score, timestamp }], total, truncated, message }, the gold-standard ACI shape.
GET/api/timelineUnified event timelineCursor-paged: ?cursor=&limit=. Merges nine sources (runs, slices, deploys, incidents, drift, memory, bugs, crucible, tempering).
GET/api/hubWebSocket upgrade for live eventsHTTP GET returns hub status + client count; same path accepts Upgrade: websocket for streaming.

Memory (L1 / L2 / L3)

The capture-and-recall surface that backs OpenBrain integration and the auto-skills system. See Chapter 22 — How the Shop Remembers for the architectural overview.

MethodPathPurposeNotes
GET/api/memoryMemory landing, recent captures + stateDashboard primary view.
GET/api/memory/reportAggregate statsCaptures/day, hit rate, top thoughts.
POST/api/memory/searchSearch L2 captures (and L3 if OpenBrain configured)Body: { query, limit, source? }.
POST/api/memory/captureCapture a thoughtBody: { content, tags, source }. Broadcasts memory-captured hub event.
POST/api/memory/drainDrain pending memory queueForces a flush of buffered captures to disk + L3.
GET/api/memory/presetsCapture-rule presetsPredefined tag bundles (debugging, architecture, etc.).
GET/api/brain/statsOpenBrain integration statsL3 connection state, capture count, embedding model.

Crucible (idea smelting)

The conversational planner surface. The full lifecycle is submit → ask → preview → finalize. See Chapter 5 — Crucible (Idea Smelting) for the workflow.

MethodPathPurposeNotes
POST/api/crucible/submitStart a new smeltBody: { idea, source? }. Returns { smeltId, firstQuestion }.
POST/api/crucible/askAnswer current question, get nextBody: { smeltId, answer, questionId? }. Mismatched questionId returns 409 ASK_QUESTION_MISMATCH.
GET/api/crucible/previewRender current draft + unresolved fieldsQuery: ?smeltId=. Returns plan draft + criticalGaps[].
POST/api/crucible/finalizeAtomically claim phase + write plan fileReturns 409 + criticalGaps[] if gaps remain; 409 + PLAN_ALREADY_EXISTS if file exists (pass overwrite: true).
POST/api/crucible/abandonMark smelt abandonedFrees the phase number for the next smelt.
GET/api/crucible/listList all smelts (filter by status)Query: ?status=draft|finalized|abandoned.
GET/api/crucible/configRead Crucible configInterview model, question budget, autopilot threshold.
POST/api/crucible/configWrite Crucible configPartial updates merged into .forge.json#crucible.
GET/api/crucible/manual-importsList manually-imported smeltsSpec Kit, hand-authored briefs.
GET/api/crucible/governanceGovernance summaryAutopilot rate, fallback rate, mean question count.

LiveGuard (drift, incidents, deploys)

The production-companion surface. Every endpoint here emits at least one event in the LiveGuard event family; subscribe over the hub to see real-time alerts.

MethodPathPurposeNotes
GET/api/driftCurrent drift score vs architecture rulesReturns { score, breakdown, asOf }. Score range 0–100.
GET/api/drift/historyDrift trend over timeOne entry per forge_drift_report invocation.
GET/api/incidentsList incidents (severity, MTTR)Sorted newest first; includes resolution timestamp + MTTR ms.
POST/api/incidentCapture a new incidentBody: { title, severity, source, body }. Emits liveguard-incident.
GET/api/deploy-journalList deploysVersion, deployer, notes, linked run.
POST/api/deploy-journalRecord a deployBody: { version, deployer, notes, runIdx? }.
POST/api/regression-guardRun regression gates against codebaseBody: { scope, baseline? }. Returns pass/fail per rule.
GET/api/runbooksList operational runbooksOne per alert class.
POST/api/runbookGenerate or update a runbookBody: { alertClass, content }.
GET/api/health-trendHealth DNA aggregatorDrift + cost + incidents + test pass-rate over time.
GET/api/hotspotsGit churn hotspotsFiles with high change frequency, refactor candidates.
GET/api/triagePrioritized alert listDrift + incidents + secrets + deps, ranked.
GET/api/liveguard/tracesLiveGuard execution tracesOne per forge_liveguard_run invocation.
GET/api/secret-scanLatest secret-scan resultsValues redacted; returns { findings: [{ file, line, severity }] }.
POST/api/secret-scan/runTrigger a fresh scanBody: { paths? }. Default scans full repo.
GET/api/deps/watchLatest dependency-vuln snapshotReturns CVE list grouped by package.
POST/api/deps/watch/runTrigger a fresh dep scanBody: { packageManager? }; auto-detects if omitted.
GET/api/env/diffEnv-var key divergence across .env filesCatches the "key in dev but missing in prod" footgun.

Quorum and fix proposals

The bridge between LiveGuard findings and structured remediation. Quorum prompts gather context across drift/incident/deploy/secret findings; fix proposals materialize that context into an actionable plan slice.

MethodPathPurposeNotes
GET/api/fix/proposalsList fix proposalsSorted by recency; filter by ?status=.
POST/api/fix/proposeGenerate an actionable fix planBody: { findingId, model? }. Returns proposed plan-slice diff.
GET/api/quorum/promptRead XSS-validated quorum promptQuery: ?promptId=. Output is HTML-escaped for safe rendering.
POST/api/quorum/promptBuild a quorum promptBody: { findings: [...], mode }. Returns { promptId, url }.

Tempering and bugs

The bug-registry surface. Tempering scans for TODO/FIXME/stub markers and produces an artifact; the bug stub endpoint converts a finding into a registered bug. Bug create/update/validate is MCP-only; see Appendix Q.

MethodPathPurposeNotes
GET/api/tempering/artifactLatest tempering artifactScan results + temper score.
POST/api/tempering/bug-stubCreate a bug stub from a findingBody: { findingId, title? severity? }.
GET/api/bugs/listList registered bugsQuery: ?status=&severity=&plan=.

Skills (decision tray)

Auto-skills surface decisions that the orchestrator wants a human to make, tag the deferred work, accept/reject the proposal, or defer for later review.

MethodPathPurposeNotes
GET/api/skillsSkill catalogIncludes hand-authored .github/skills/*/SKILL.md and auto-skills.
GET/api/skills/pendingPending decisions awaiting accept/rejectQuery: ?source=.
POST/api/skills/acceptAccept a pending decisionBody: { decisionId, note? }.
POST/api/skills/rejectReject a pending decisionBody: { decisionId, reason? }.
POST/api/skills/deferDefer a pending decisionBody: { decisionId, untilTimestamp? }.

Inner loop

The self-improvement surface. Inner-loop subsystems observe runs and propose tightenings: gate suggestions from observed failures, reviewer-score calibration, cost-anomaly detection, federation across sibling repos.

MethodPathPurposeNotes
GET/api/innerloop/statusAll inner-loop subsystem statesReturns rollup of the six subsystems below.
GET/api/innerloop/reviewer-calibrationReviewer-score calibration traceDrift between auto-reviewer and human override decisions.
GET/api/innerloop/gate-suggestionsGate-tightening suggestionsPatterns where current gates allowed regressions.
GET/api/innerloop/cost-anomaliesCost anomalies across runsSlices that cost >3σ above their plan baseline.
GET/api/innerloop/proposed-fixesAuto-proposed fixes from health-trend signalsCombines drift, incidents, and test trends.
GET/api/innerloop/federationFederation-mode statusAdvisory cross-repo learning when configured.
POST/api/innerloop/federation/toggleEnable/disable federationBody: { enabled }.

Bridge and approvals

The human-in-the-loop surface. When a plan slice is flagged for approval (assisted mode or escalation), the orchestrator emits an approval-requested event and waits. The browser-link variant is opened by VS Code notification; the POST variant is for programmatic clients.

MethodPathPurposeNotes
GET/api/bridge/statusPending approvals waiting for a human nudgeReturns { pending: [{ runId, sliceId, reason, createdAt }] }.
POST/api/bridge/approve/:runIdProgrammatic approvalHeader X-Bridge-Token required (HMAC from PFORGE_BRIDGE_SECRET). Body: { decision: "approve"|"reject", note? }.
GET/api/bridge/approve/:runIdBrowser-link approvalQuery ?token= with same HMAC; renders a confirm page. Used by VS Code notification & email links.
Bridge tokens are the only cross-boundary auth. Set PFORGE_BRIDGE_SECRET in your environment (see Appendix U) before enabling assisted runs. Approvals without a valid token return 401 BRIDGE_TOKEN_INVALID.

Copilot integration

The surface that powers the Copilot Integration Trilogy, reading, previewing, and syncing .github/copilot-instructions.md from the project profile + principles. OpenClaw endpoints post LiveGuard snapshots to the optional analytics service.

MethodPathPurposeNotes
GET/api/copilot-instructionsRead current fileReturns raw markdown.
POST/api/copilot-instructions/previewPreview a regenerated fileBody: { projectProfile? principles? }. Non-destructive.
POST/api/copilot-instructions/syncSync from project profile + principlesWrites the file; emits a hub event for editor refresh.
POST/api/openclaw/snapshotPost a LiveGuard snapshot to OpenClawBody: snapshot envelope. Requires openclaw.endpoint in .forge.json.
GET/api/openclaw/configOpenClaw endpoint + auth configToken is masked in response.

GitHub and team coordination

Team-mode endpoints that wrap the gh CLI for read-only GitHub access plus a per-operator activity feed sourced from .forge/team-activity.jsonl.

MethodPathPurposeNotes
GET/api/github-metricsLive GitHub repo metricsPRs open, stale branches, issue load. Requires gh auth login.
GET/api/github-readinessReadiness for Copilot Coding Agent dispatchValidates labels, branch protection, repo settings.
GET/api/team-dashboardPer-operator stats + conflict riskAggregates team-activity.jsonl.
GET/api/team-activityRecent run summaries from team feedCursor-paged: ?cursor=&limit=.

Notifications, audit, dashboard, settings

The "everything else" administrative surface, notification channels, audit drain loop, dashboard state persistence, config + secrets read/write, extensions, update checks, soft restart.

MethodPathPurposeNotes
GET/api/notifications/configNotification channel configSlack, Teams, PagerDuty, Email per .forge.json.
POST/api/notifications/configUpdate channelsBody: partial config; deep-merged.
GET/api/audit/configAudit drain loop configReturns drain interval, ring sizes, destinations.
PUT/api/audit/configUpdate audit configFull replacement of audit subtree.
POST/api/audit/drainTrigger one full drain passUseful before shutdown.
GET/api/dashboard-stateSticky dashboard tab + filter statePer-user UI prefs.
POST/api/dashboard-statePersist dashboard stateBody: { tab, filters, layout }.
GET/api/configRead merged .forge.jsonAfter env-var overlay and computed defaults.
POST/api/configUpdate configBody: partial; deep-merged. Writes .forge.json.
GET/api/secretsRead .forge/secrets.json keysValues masked; only key presence returned.
POST/api/secretsUpdate local secrets storeBody: { key, value }. Writes the gitignored file.
GET/api/extensionsInstalled extensionsFrom .forge/extensions/.
GET/api/update-statusUpdate-check statusLatest release, currency, channel.
POST/api/self-updateTrigger self-update installRuns pforge self-update; restart required afterward.
POST/api/server/restartSoft-restart the MCP serverHMR-friendly: re-loads code without dropping the WebSocket clients (best-effort).

Generic MCP dispatcher

The escape hatch. Any of the 106 MCP tools can be invoked over REST through this surface, useful for SDK clients, CI scripts, and any external integration that needs richer tool semantics than the first-class endpoints expose.

MethodPathPurposeNotes
POST/api/tool/:nameInvoke any of the 106 MCP tools over RESTBody is the tool's input contract (see Appendix Q). Response is the tool's output payload, unwrapped from the MCP envelope. Crucible and Forge-Master tools route through the MCP handler (v2.82.1 fix).
POST/api/tool/org-rulesAliased convenience, forge_org_rulesEquivalent to POST /api/tool/forge_org_rules.
POST/api/tool/run-planAliased convenience, forge_run_planEquivalent to POST /api/tool/forge_run_plan; also surfaced as /api/runs/trigger with a friendlier shape.
When to use the dispatcher vs. the first-class endpoint. If both exist (e.g. POST /api/runs/trigger and POST /api/tool/forge_run_plan), prefer the first-class endpoint, its response shape is tailored for direct rendering and skips the MCP envelope. Use the dispatcher when the tool has no first-class equivalent (most estimation, bug, and lattice tools).

Forge-Master (conversational entrypoint)

The HTTP surface for the conversational classifier described in the Forge-Master chapter. Lives alongside the main API on the same port; chat sessions are persistent and resumable.

MethodPathPurposeNotes
GET/api/forge-master/capabilitiesClassifier + tool surface metadataWhat Forge-Master can do.
GET/api/forge-master/promptsSuggested starter promptsSurfaced by the dashboard chat panel.
GET/api/forge-master/sessionsList active chat sessionsReturns { sessions: [{ id, summary, lastTurnAt }] }.
GET/api/forge-master/session/:idFetch one sessionFull turn history.
POST/api/forge-master/chatStart a chat or send a turnBody: { sessionId? message }. Returns { sessionId, response }.
GET/api/forge-master/chat/:sessionId/streamServer-Sent Events stream of a turnFor incremental rendering.
POST/api/forge-master/chat/:sessionId/approveApprove a Forge-Master tool callFor tools requiring human approval (e.g. write actions).
GET/api/forge-master/prefsRead user preferencesTone, verbosity, classifier mode.
PUT/api/forge-master/prefsUpdate preferencesBody: partial prefs.
GET/api/forge-master/cache-statsEmbedding cache livelinessHit rate, useful as a Forge-Master health probe.

Image generation

The single image-generation endpoint. Routes to xAI Grok Aurora (if XAI_API_KEY is set) or OpenAI DALL-E (if OPENAI_API_KEY is set). Auto-detects the available provider.

MethodPathPurposeNotes
POST/api/image/generateGenerate an imageBody: { prompt, size? count? provider? }. Returns { images: [{ url, b64? }] }.

Worked examples

Five short recipes that cover the most common external-integration patterns. All examples assume the server is running at http://127.0.0.1:3100.

1. Trigger a plan run from a shell script

curl -X POST http://127.0.0.1:3100/api/runs/trigger \
  -H 'Content-Type: application/json' \
  -d '{
        "plan":   "docs/plans/Phase-28-PLAN.md",
        "mode":   "auto",
        "quorum": "auto"
      }'

# Returns: { "runIdx": 47, "pid": 18432 }

2. Stream live events with wscat

wscat -c ws://127.0.0.1:3100/api/hub

> {"type":"hello"}
< {"version":1,"type":"connected","timestamp":"2025-06-15T12:34:56.789Z","source":"hub"}
< {"version":1,"type":"slice-started","timestamp":"...","source":"orchestrator", ...}

Full event catalog in Appendix V.

3. Search across memory, plans, and bugs

curl 'http://127.0.0.1:3100/api/search?query=anvil+cache&source=memory&limit=10'

# Returns the gold-standard ACI shape:
# {
#   "hits":      [ { source, recordRef, snippet, score, timestamp } ],
#   "total":     27,
#   "truncated": true,
#   "message":   "Showing 10 of 27 hits across source=memory."
# }

4. Invoke any MCP tool generically

curl -X POST http://127.0.0.1:3100/api/tool/forge_estimate_quorum \
  -H 'Content-Type: application/json' \
  -d '{ "plan": "docs/plans/Phase-28-PLAN.md" }'

# Returns the tool's output payload unwrapped from the MCP envelope:
# { "modes": { "auto": {...}, "power": {...}, "speed": {...}, "false": {...} } }

5. Approve a bridge-paused run from a browser link

# VS Code notification or email link contains:
# https://127.0.0.1:3100/api/bridge/approve/47?token=<HMAC>
#
# Clicking opens a confirm page that POSTs back with the decision.
# Programmatic equivalent:

curl -X POST http://127.0.0.1:3100/api/bridge/approve/47 \
  -H 'Content-Type: application/json' \
  -H 'X-Bridge-Token: <HMAC>' \
  -d '{ "decision": "approve", "note": "Looks good, ship it" }'

Using the SDK instead

The pforge-sdk wraps the REST API with typed helpers. Prefer it when integrating from JavaScript/TypeScript:

import { client } from 'pforge-sdk';

const c = client({ baseUrl: 'http://127.0.0.1:3100' });

const runs     = await c.get('/api/runs/latest');
const estimate = await c.callTool('forge_estimate_quorum', {
  plan: 'docs/plans/Phase-28-PLAN.md',
});

See also