Skip to main content
Enoch serves control-plane and worker APIs from FastAPI apps that commonly listen on port 8787. All endpoints except root GET /healthz, GET /dashboard, and GET /control/dashboard require a bearer token. The control-plane health endpoint is GET /control/health and does require authentication. The dashboard shell is unauthenticated for the initial page load; it prompts for a bearer token and stores it in localStorage for subsequent API calls. Read the Authentication section below before making your first request. This page maps the main endpoint groups present in the local source snapshot. It is not a generated OpenAPI reference; verify request and response models against the code before building clients. The operator dashboard now prefers bounded /control/api/v1/* read models, while older broad status surfaces remain available for compatibility and debug. For current runtime topology and compatibility boundaries, see current runtime snapshot.

API versioning

  • /control/api/v1/* is the preferred bounded read API for dashboards, scripts, and external integrations.
  • Older broad status and dashboard endpoints are compatibility/debug surfaces.
  • Command endpoints are behavior-stable, but clients should verify request and response models against source until generated OpenAPI docs are published.
  • Legacy /omx/event remains available as an event-ingest compatibility hook; new docs should describe the product as Enoch Control Plane.

Authentication

Unauthenticated routes include GET /healthz, GET /dashboard, GET /control/dashboard, and GET /favicon.ico. Dashboard data APIs and most control-plane routes require a bearer token or an explicit token bootstrap path where the dashboard code supports it.
Authorization: Bearer <control_api_bearer_token>
Pass the token in every request:
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:8787/control/api/status
The dashboard stores the token in browser localStorage after the operator enters and saves it. Do not pass bearer tokens in dashboard URLs.

GET /healthz

No authentication required. Returns a minimal app-level liveness check. Use authenticated GET /control/health when you need the control-plane database path and control-plane service timestamp.
curl -fsS http://127.0.0.1:8787/healthz
Response:
{"ok": true, "service": "enoch_worker_gate", "timestamp": "2025-05-01T12:00:00+00:00"}

GET /control/health

Requires bearer authentication. Returns control-plane health, the active store backend, and the configured database path (SQLite file path for local dev; Postgres adapter URL or label for production).
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:8787/control/health
Response:
{
  "ok": true,
  "service": "enoch-langgraph-control-plane",
  "db_path": "/var/lib/enoch-control-plane/state/control_plane.sqlite3",
  "store_backend": "sqlite",
  "timestamp": "2025-05-01T12:00:00+00:00"
}
Production on enoch-core sets store_backend to supabase with local Postgres; treat that value as a compatibility adapter label, not Supabase Cloud.

GET /control/api/status

Returns a compatibility dispatch-safety snapshot aggregated from the control-plane database. Add ?refresh_worker=true to trigger a fresh worker preflight before responding. For bounded operator cards, prefer GET /control/api/v1/overview.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  "http://127.0.0.1:8787/control/api/status?refresh_worker=true" \
  | python3 -m json.tool
Key response fields:
FieldDescription
dispatch_safetrue when no blockers exist and no critical conflicts are present
dispatch_blockersList of human-readable blocker strings
flags.queue_pausedtrue when the queue is paused
flags.maintenance_modetrue when maintenance mode is active
countsPer-status item counts plus papers and queue_total
active_itemsQueue rows currently in an active dispatch/run state
source_freshnessStaleness of each evidence source
warnings / conflictsStructured findings with severity, source, and suggested actions

GET /control/api/queue-health

Returns queue health with worker freshness details, active run detail, latest alert check results, and recent worker callbacks. Add ?refresh_worker=true to refresh the worker preflight observation first.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  "http://127.0.0.1:8787/control/api/queue-health?refresh_worker=true" \
  | python3 -m json.tool
The v1 read models are the preferred dashboard and integration surface. They answer operator questions with pagination, hard caps, and bounded SQL reads.

GET /control/api/v1/overview

Returns the operator overview cards, the top actionable items, and recent events. This is the main bounded dashboard snapshot.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  "http://127.0.0.1:8787/control/api/v1/overview?active_limit=5&event_limit=10" \
  | python3 -m json.tool
Key overview fields include:
FieldMeaning
operator_countsCanonical operator lanes such as attention, running, write, publish, and imported
operator_detail_countsDrill-down compatibility/detail counts
paper_pipeline.write_neededActionable positive paper work only
paper_pipeline.raw_completed_no_paper_candidatesInformational/debug rows before the decision gate
paper_pipeline.not_writable_by_decision_gateRows rejected by the decision gate
paper_pipeline.publish_readyFinalized drafts missing corpus import
paper_pipeline.published_importedPapers already represented in the corpus-import ledger

GET /control/api/v1/lanes

Returns the active lane view and next candidate, with bounded active-item and count data.

GET /control/api/v1/queue

Returns a bounded queue list with cursor pagination, page_size, status, search, and sort controls.

GET /control/api/v1/runs

Returns a bounded run list with cursor pagination and filters for state, project, and search.

GET /control/api/v1/runs/{run_id}

Returns one run, its project, a bounded paper list, and a bounded event list.

GET /control/api/v1/projects/{project_id}

Returns one project, the current queue item, bounded run and paper lists, bounded events, and worker observations.

GET /control/api/v1/papers

Returns a bounded paper list with cursor pagination, status, search, and sort controls.

GET /control/api/v1/papers/{paper_id}

Returns one paper, its project, its run, and a bounded event list.

GET /control/api/v1/events

Returns a bounded audit/event list with optional entity, type, search, and payload filters.

GET /control/api/v1/observability/health

Returns route-observability health, if enabled.

GET /control/api/v1/observability/memory

Returns the controller process RSS and memory warning status.

GET /control/api/v1/automation-readiness

Returns long-haul automation readiness (READY or BLOCKED) with the first failing criterion when blocked.

GET /control/api/v1/research-quality

Returns Research Facility quality signals and admission diagnostics for the operator dashboard.

GET /control/api/v1/source-lineage

Returns source-lineage and dedupe context for intake and queue rows.

GET /control/api/v1/projects

Returns a bounded project list with cursor pagination and search filters.

POST /control/api/v1/followups/launch-next

Dry-runs or launches one bounded follow-up investigation from existing no-paper evidence. Used by queue-pump follow-up automation when enabled.

GET /control/api/queues/{queue}

Returns a paginated list of queue rows filtered to one logical queue group.
queue valueDescription
activeItems in dispatching, running, awaiting_wake, wake_received, or reconciling status
queuedItems waiting to be dispatched
blockedItems in blocked, needs_review, or dispatch_error status, or any item with manual_review_required set
pausedItems in paused status
completedItems in completed or canceled status
allAll rows regardless of status
Query parameters:
ParameterDefaultDescription
page1Page number (≥ 1)
page_size50Rows per page (1–500)
search""Full-text filter across all string fields
status""Exact QueueStatus value filter
sortdispatch_prioritySort column; prefix with - for descending (e.g. -updated_at)
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  "http://127.0.0.1:8787/control/api/queues/queued?page_size=20&search=my-project"

POST /control/dispatch-next

Selects and dispatches the next eligible candidate from the queue. Always use dry_run: true first to verify the candidate before committing a live dispatch.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"dry_run": true, "requested_by": "operator"}' \
  http://127.0.0.1:8787/control/dispatch-next \
  | python3 -m json.tool
Request body (DispatchNextRequest):
{
  "dry_run": true,
  "requested_by": "operator",
  "force_preflight": true
}
Response action values: "paused", "noop", "dry_run_dispatch", "live_dispatch", "dispatch_blocked".

POST /control/api/preflight

Runs an explicit worker preflight check without committing a dispatch. Use this to verify worker connectivity, bearer token validity, and GPU/memory headroom before live dispatch.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "wake_gate_url": "http://worker.example:8787",
    "bearer_token": "worker-api-token",
    "require_paused": false,
    "strict": false
  }' \
  http://127.0.0.1:8787/control/api/preflight \
  | python3 -m json.tool
Request body (WorkerPreflightRequest):
{
  "wake_gate_url": "http://worker.example:8787",
  "bearer_token": "",
  "require_paused": true,
  "strict": false,
  "max_gpu_pct": 5.0,
  "min_memory_available_mib": 16384
}
Response (WorkerPreflightResponse):
{
  "ok": true,
  "target": "http://worker.example:8787",
  "summary": "...",
  "checks": [
    {"name": "wake_gate_healthz", "ok": true, "detail": "...", "data": {}},
    {"name": "worker_no_live_runs", "ok": true, "detail": "...", "data": {}}
  ]
}
Optional request field machine_target selects a named entry from worker_targets. When set, the control plane resolves it to that target’s wake_gate_url, bearer token, and memory floor.

POST /control/papers/draft-next

Drafts the next decision-gated positive paper candidate. Use dry_run: true first to preview eligibility without writing artifacts.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"dry_run": true, "requested_by": "operator"}' \
  http://127.0.0.1:8787/control/papers/draft-next \
  | python3 -m json.tool
Request body (DraftNextRequest):
{
  "dry_run": true,
  "requested_by": "operator",
  "force": false
}
Response action values: "noop", "dry_run_draft", "drafted". Live calls skip candidates with missing evidence and return action: "noop" with skipped candidate details; they do not return HTTP 424.

GET /control/api/projects/{project_id}

Returns project detail aggregating the project record, current queue item, all run records, associated papers, recent events, and cached worker observations.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:8787/control/api/projects/my-project-id \
  | python3 -m json.tool
Returns 404 if neither a project record nor a queue item is found for {project_id}.
Paper automation routes are mounted at both /control/api/publication-automation/* and /control/api/paper-reviews/*. Prefer publication-automation in new integrations.

GET /control/api/papers

Returns the full paper queue as a paginated list.Query parameters:
ParameterDefaultDescription
page1Page number
page_size50Rows per page (1–500)
search""Full-text filter
status""Exact paper_status filter
sort-updated_atSort column; prefix with - for descending (e.g. paper_status)
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  "http://127.0.0.1:8787/control/api/papers?page_size=100"

GET /control/api/paper-reviews

Returns publication automation detail rows sorted by rank_score descending by default. Raw review-like values are compatibility/detail states for APIs and historical rows; the primary operator workflow is write -> finalize -> publish/import, with publish/import gated by the corpus-import ledger.Query parameters:
ParameterDefaultDescription
page1Page number
page_size50Rows per page (1–500)
search""Full-text filter
review_status""Raw compatibility/detail filter. Values include triage_ready, unreviewed, in_review, changes_requested, blocked, approved_for_finalization, finalized, and rejected. Do not present these as the operator workflow.
paper_status""One of eligible, draft_generating, draft_review, publication_generating, publication_draft, human_review_required, archived
include_rank_reasonstrueInclude per-item rank reason arrays
sort-rank_scoreSort column; prefix with - for descending (e.g. rank_score)
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  "http://127.0.0.1:8787/control/api/paper-reviews?paper_status=publication_draft&include_rank_reasons=true"

GET /control/api/paper-reviews/next

Returns the highest-ranked publication automation detail item matching the given filters. Defaults to paper_status=publication_draft, excluding finalized and rejected items.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  "http://127.0.0.1:8787/control/api/paper-reviews/next?paper_status=publication_draft"
Returns 404 if no matching item exists.

POST /control/api/paper-reviews/{paper_id}/rewrite-draft

Rewrites the paper draft artifacts for the given paper using the configured AI writer (e.g. GLM-5.1 via Synthetic.new). If paper_evidence_sync_enabled is true, the control plane syncs evidence from the worker before rewriting. When sync is enabled and local evidence is still missing, live calls return HTTP 424 with an evidence_sync detail object.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "rewrite-my-paper-001",
    "requested_by": "operator",
    "force": true
  }' \
  http://127.0.0.1:8787/control/api/paper-reviews/my-paper-id/rewrite-draft
Request body (PaperReviewRewriteDraftRequest):
{
  "idempotency_key": "paper-review-rewrite:<timestamp>",
  "requested_by": "operator",
  "force": true
}

POST /control/api/paper-reviews/rewrite-batch

Rewrites up to limit papers in a single synchronous batch call. The request stays open until all papers are processed; a 10-paper batch may take several minutes.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "bulk-rewrite-001",
    "requested_by": "operator",
    "paper_status": "publication_draft",
    "limit": 10,
    "force": true,
    "dry_run": false,
    "skip_rewritten": true
  }' \
  http://127.0.0.1:8787/control/api/paper-reviews/rewrite-batch
Request body (PaperReviewBulkRewriteRequest):
{
  "idempotency_key": "paper-review-bulk-rewrite:<timestamp>",
  "requested_by": "ai-publication-pipeline",
  "paper_status": "publication_draft",
  "review_status": "",
  "search": "",
  "limit": 10,
  "force": true,
  "dry_run": false,
  "skip_rewritten": true
}
limit is capped at 200 by the model, but the dashboard uses a default of 10. Set dry_run: true to preview which papers would be selected without triggering any rewrites.

POST /control/api/paper-reviews/{paper_id}/prepare-finalization-package

Assembles the finalization package for a publication automation item. Use dry_run: true to preview the package manifest without writing the package. Use after the automated packaging checklist has passed.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "package-my-paper-001",
    "requested_by": "operator",
    "target_label": "public-release",
    "dry_run": true
  }' \
  http://127.0.0.1:8787/control/api/paper-reviews/my-paper-id/prepare-finalization-package

POST /control/api/paper-reviews/{paper_id}/approve-finalization

Compatibility endpoint with a legacy path name. It marks a paper automation item as ready for finalization; it is not the normal operator gate. Requires idempotency_key and requested_by.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "approve-my-paper-001",
    "requested_by": "operator",
    "note": ""
  }' \
  http://127.0.0.1:8787/control/api/paper-reviews/my-paper-id/approve-finalization

POST /control/api/paper-reviews/{paper_id}/status

Updates the raw review_status automation state for a paper item. This endpoint is retained for compatibility/detail workflows; it should not be presented as the public or operator paper path.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "status-my-paper-001",
    "requested_by": "operator",
    "review_status": "in_review",
    "note": "",
    "blocker": ""
  }' \
  http://127.0.0.1:8787/control/api/paper-reviews/my-paper-id/status
Valid raw review_status values: unreviewed, triage_ready, in_review, changes_requested, blocked, approved_for_finalization, finalized, rejected. Public/operator copy should describe these as automation/detail states, not the operator lane labels. The primary operator model is write -> finalize -> publish/import.

POST /control/api/paper-reviews/{paper_id}/checklist/{item_id}

Updates the status of a single packaging/finalization checklist item.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "checklist-my-paper-item1-001",
    "requested_by": "operator",
    "status": "pass",
    "note": ""
  }' \
  http://127.0.0.1:8787/control/api/paper-reviews/my-paper-id/checklist/item1
Valid status values: pending, pass, fail, accepted_risk, not_applicable.

GET /control/api/events

Returns the append-only audit event log. Supports filtering by entity_type, entity_id, event_type, and full-text search.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  "http://127.0.0.1:8787/control/api/events?page_size=200"
Query parameters:
ParameterDefaultDescription
page1Page number
page_size100Rows per page (1–500)
entity_type""Filter by entity type (e.g. paper_review)
entity_id""Filter by entity ID
event_type""Filter by event type (e.g. paper_review.draft_rewritten)
search""Full-text filter

GET /control/api/intake/notion

Returns the latest intake observation for the legacy Notion sync path, the current queued-project projection, skipped-row reason counts, and recent intake events. Current control-plane intake should normally be verified through the idea workbench and queue tables instead of this compatibility endpoint.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:8787/control/api/intake/notion

POST /control/api/worker-callback

Receives idempotent worker completion callbacks from the worker gate. Callbacks use an idempotency_key; duplicate keys with the same payload are accepted, conflicting payloads return 409.
These endpoints are served by the worker process and are called by the agent runtime and by the control plane during live dispatch.

POST /omx/event

Receives legacy OMX-compatible/Codex lifecycle events from the agent runtime. The worker gate uses these events to track process state and determine when a quiet window has been reached.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "session-end",
    "run_id": "my-project-20250501T120000Z",
    "session_id": "sess-abc123",
    "project_id": "my-project",
    "project_name": "My Project"
  }' \
  http://worker.example:8787/omx/event
Request body (ControlPlaneEvent; OmxEvent remains a deprecated compatibility alias) key fields:
{
  "event": "session-end",
  "run_id": "...",
  "session_id": "...",
  "project_id": null,
  "project_name": null,
  "question": null,
  "root_pid": null,
  "process_group_id": null,
  "timestamp": "2025-05-01T12:00:00+00:00",
  "tmux_session": null,
  "message": null
}
Valid event values: session-start, session-idle, session-stop, session-end, ask-user-question.

POST /prepare-project

Prepares the project workspace on the worker before dispatch. Called automatically by the control plane’s live dispatch path.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "run_id": "my-project-20250501T120000Z",
    "project_id": "my-project",
    "project_name": "My Project",
    "project_dir": "my-project",
    "prompt_file": "my-project/prompts/initial.md",
    "prompt_text": "# Enoch Research Action...",
    "overwrite": true
  }' \
  http://worker.example:8787/prepare-project

POST /dispatch

Dispatches an agent run to the worker. Called automatically by the control plane’s live dispatch path after a successful prepare.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "run_id": "my-project-20250501T120000Z",
    "project_id": "my-project",
    "project_dir": "my-project",
    "prompt_file": "my-project/prompts/initial.md",
    "mode": "exec",
    "model": "gpt-5.5",
    "reasoning_effort": "medium",
    "sandbox": "danger-full-access"
  }' \
  http://worker.example:8787/dispatch
Valid mode values: exec, resume. Valid reasoning_effort values: low, medium, high, xhigh.

GET /control/dashboard

Returns the browser-based Enoch control dashboard shell as an HTML page. No authentication is required to load the page; the dashboard prompts for a bearer token and stores it in localStorage.
open http://127.0.0.1:8787/control/dashboard

GET /dashboard

Redirects to /control/dashboard.

GET /dashboard/api

Returns the full legacy worker dashboard snapshot as JSON. Use bounded /control/api/v1/* endpoints for primary operator dashboards and integrations.Query parameters:
ParameterDefaultDescription
limitMaximum run rows to include
event_limitMaximum event rows to include
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  "http://worker.example:8787/dashboard/api?limit=50&event_limit=100"
Enoch Core endpoints are prefixed /enoch-core and all require authentication.

GET /enoch-core/health

Returns the current Enoch Core mode and database path.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:8787/enoch-core/health
Response (HealthResponse):
{
  "ok": true,
  "service": "enoch_core",
  "mode": "shadow",
  "db_path": "/var/lib/enoch-control-plane/state/enoch_core.sqlite3",
  "timestamp": "2025-05-01T12:00:00+00:00"
}
Valid mode values: off, shadow, compare, enforce.

POST /enoch-core/snapshots/n8n-queue

This endpoint retains its legacy n8n-queue name for backward compatibility, but n8n is no longer used. Any source can post queue snapshots.
Ingests a queue snapshot from any compatible source. The idempotency_key is required; the same key with the same payload is accepted idempotently, but the same key with a different payload returns 409.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "snapshot-2025-05-01T120000Z",
    "source": "n8n",
    "mode": "shadow",
    "queue_rows": [],
    "paper_rows": [],
    "captured_at": "2025-05-01T12:00:00+00:00"
  }' \
  http://127.0.0.1:8787/enoch-core/snapshots/n8n-queue
Response (SnapshotIngestResponse):
{
  "ok": true,
  "mode": "shadow",
  "inserted": true,
  "event_id": 42,
  "snapshot_id": 7,
  "queue_rows": 0,
  "paper_rows": 0,
  "would_apply": false,
  "message": "snapshot recorded locally only; no external side effects performed"
}

GET /enoch-core/projections/queue

Returns the queue projection computed from the latest ingested snapshot.
curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:8787/enoch-core/projections/queue
Response (QueueProjection) key fields:
{
  "ok": true,
  "mode": "shadow",
  "projection_version": "enoch-core.queue.v1",
  "total_queue_rows": 0,
  "total_paper_rows": 0,
  "status_counts": {},
  "active_rows": [],
  "draft_candidate_count": 0,
  "polish_candidate_count": 0,
  "warnings": []
}