Monitor HTTP API v1¶
Moraine exposes a read-only HTTP API for the monitor dashboard and other local
monitor clients. The canonical base path is /api/v1. API versioning applies to
the HTTP surface only; it does not change the MCP transport or MCP tool schemas.
The examples below use relative paths because the bundled dashboard calls the API on the same origin that served the dashboard.
Route Matrix¶
All canonical routes are GET routes and successful responses use JSON.
| Route | Query | Purpose |
|---|---|---|
/api/v1/capabilities |
none | Describe this server build, its observed schema migration level, and available HTTP feature groups. |
/api/v1/health |
none | Probe ClickHouse health and report a compact ingest heartbeat summary. |
/api/v1/status |
history |
Return ClickHouse/database diagnostics plus typed ingestion health, finite historical coverage, optional durable progress history, conservative ETA, and active alerts. |
/api/v1/analytics |
range |
Return token, turn, and concurrent-session time series for a supported window. |
/api/v1/tables |
none | List tables with engine, temporary-table marker, and estimated row count. |
/api/v1/tables/:table |
limit |
Return the named table's schema and a bounded row preview. :table is a path parameter. |
/api/v1/web-searches |
limit |
Return a bounded list of normalized web-search activity. |
/api/v1/sessions |
since, limit |
Return bounded session analytics, including the turns and steps used by the dashboard. |
There is no session-detail backend route. In particular,
/api/v1/sessions/:id is not part of the API. A client that needs a session
must use the records returned by /api/v1/sessions; it must not infer a detail
route from the collection URL.
Session title uses the shared display-label precedence: explicit metadata
title/name, then the first genuine Codex event_msg/user_message, followed by
existing metadata and a privacy-safe descriptor. Injected Codex
response_item/message role=user content is not eligible for the prompt tier.
Prompt-derived labels are trimmed to the first line and capped at 120 Unicode
scalar values plus an ellipsis.
Capabilities Contract¶
GET /api/v1/capabilities returns HTTP 200 with this contract:
{
"ok": true,
"server_version": "0.0.0",
"schema_migration_level": "017",
"features": {
"analytics": true,
"sessions": true,
"table_inspection": true,
"web_searches": true
}
}
The example version values are illustrative. Field semantics are:
okis alwaystruefor a successfully generated capabilities document.server_versionis the version of the running Moraine server build. It is not the ClickHouse version.schema_migration_levelis the latest applied Moraine migration identifier the server can observe. Migration identifiers are opaque strings; clients must not parse them as integers. It isnullwhen no applied migration level is available, including when the database or migration ledger is absent or cannot be read. Thatnullis capability metadata, not by itself an HTTP error.features.analyticsmeans the analytics collection route is implemented.features.sessionsmeans session collection is implemented. It does not advertise a session-detail route.features.table_inspectionmeans table listing and bounded table preview are implemented.features.web_searchesmeans the normalized web-search collection is implemented.
Feature values advertise route support, not backend health or the presence of
rows. A feature can be true while a particular read returns an empty result
or a backend error.
The features object is additive. Clients must test the keys they understand
and ignore unrecognized feature keys. Adding another feature key does not, on
its own, require a new API version. The top-level fields and the four feature
keys shown above are the required v1 contract.
Query Parameters and Bounds¶
Limits are parsed as unsigned decimal integers and then clamped. A value below
the lower bound, including 0, becomes the lower bound; a value above the
upper bound becomes the upper bound. A value that cannot be parsed as an
unsigned integer is a malformed query and returns HTTP 400.
| Route | Parameter | Default | Accepted or effective values |
|---|---|---|---|
/api/v1/status |
history |
0 |
clamped to 0..=120; omitted or 0 performs no history read |
/api/v1/analytics |
range |
24h |
15m, 1h, 6h, 24h, 7d, or 30d |
/api/v1/sessions |
since |
30d |
1h, 6h, 24h, 7d, 30d, 90d, or all |
/api/v1/sessions |
limit |
50 |
clamped to 1..=200 |
/api/v1/web-searches |
limit |
100 |
clamped to 1..=1000 |
/api/v1/tables/:table |
limit |
25 |
clamped to 1..=500 |
An unknown range does not produce an error; it resolves to 24h. An unknown
since similarly resolves to 30d. Responses report or embody the resolved
window, so clients should treat the supported values above as the request
contract rather than relying on fallback behavior.
Status history is the exception to positive collection limits: zero is meaningful and preserves the cheap latest-only status poll. When requested, history contains at most 120 oldest-to-newest narrow checkpoint points for the selected ingestor run; it does not repeat host data, source paths, errors, or backend sink payloads.
The table path parameter must match the strict ASCII identifier pattern
[A-Za-z_][A-Za-z0-9_]*. A rejected identifier returns HTTP 400 with
code: "invalid_request". A syntactically valid name that cannot be read from
the configured database is a backend read failure, not a 404 resource
response.
Successful Empty and Nullable Values¶
Collection routes use empty arrays when a successful query has no matching
rows. They do not use null to mean an empty collection.
null marks unavailable or unobserved optional diagnostics:
capabilities.schema_migration_levelisnullunder the conditions described in the capabilities contract./api/v1/statuscan return HTTP200andok: truewhileclickhouse.healthyisfalse. In that diagnostic response, unavailableclickhouse.versionorclickhouse.ping_msvalues arenullandclickhouse.errorexplains the probe result. If the database does not exist, its table list is empty.- Health and status connection diagnostics use
connections.total: nullwith a siblingconnections.errorwhen connection metrics are unavailable. A connection-metrics error alone does not make an otherwise successful health probe fail. - When no ingest heartbeat is available,
ingestor.presentandingestor.alivearefalse, whileingestor.latestandingestor.age_secondsarenull. ingest_statusisnullwhen no database/status is available in an otherwise successful diagnostic response. A typed repository read failure is instead a non-2xx response as described below. When present, its independenthealth,coverage,freshness, andreadinessconditions usetrue,false, orunknownstates with stable reason codes.query_pressureis always present in a successful status response. Itsscopeisprocess;interactive,background,migration, andadministrativeeach report currentrunning/queuedcounts and a cumulativerejectedcount.resource_limit_eventsis cumulative for that backend process. No counter or label contains SQL, query text, credentials, or user content.ingest_status.heartbeat.latest.progressis absent on pre-034 heartbeat rows. A present progress snapshot freezes the startup file/byte denominator for one ingestor instance and advances only after checkpoint rows are durably committed. SQLite sources without a byte watermark report completed-file coverage;coverage_basis: "files"identifies that measurement without degrading an otherwise complete, healthy ingest run.ingest_status.historyis omitted whenhistoryis omitted or zero. Requested history points contain timestamp, queue/active pressure, queue capacity, sink row/retry pressure, discovery state, and durable file/byte completion counters.ingest_status.rateandingest_status.etaarenulluntil at least six same-instance observations cover 30 seconds with stable throughput. ETA is also suppressed during discovery, sink retries, zero progress, and excessive short/long-window variance.
Clients must distinguish a diagnostic value inside an HTTP 200 response from
an endpoint failure represented by a non-2xx status.
Errors and Status Codes¶
API handlers use JSON errors with at least this envelope:
An endpoint can add diagnostic fields to that minimum envelope. code is a
stable machine-readable category; error message text is narrative operator
context and can include backend details. For example, a health failure also
reports its configured database information and connection diagnostics. Clients
should branch on the HTTP status and code, not match arbitrary message text.
| Status | Meaning |
|---|---|
200 OK |
The request completed. Inspect diagnostic fields such as clickhouse.healthy; 200 does not mean every component is healthy. |
400 Bad Request |
Request/repository validation failed (invalid_request), including a rejected table identifier. Framework-generated malformed-query responses are not guaranteed to use the application JSON envelope. |
403 Forbidden |
Static-file path traversal or a path that resolves outside the configured static root. The JSON error is {"ok":false,"error":"forbidden"}. |
404 Not Found |
No requested static file exists. The JSON error is {"ok":false,"error":"not found"}. |
405 Method Not Allowed |
The path exists but does not support the requested HTTP method. A JSON error envelope is not guaranteed. |
429 Too Many Requests |
Admission is full (busy), or a ClickHouse memory, disk, row, or byte limit was reached (resource_exhausted). |
499 Client Closed Request |
Admitted repository work was cancelled (cancelled) while an HTTP response was still possible. A fully disconnected client cannot receive this response. |
500 Internal Server Error |
A Moraine invariant/implementation failed (internal_error), or the static root/file cannot be resolved/read. |
503 Service Unavailable |
A required repository, ClickHouse, or transport operation failed (backend_failure). |
504 Gateway Timeout |
An explicitly supplied absolute caller deadline expired (deadline_exceeded). Current Monitor requests do not supply one, and Moraine adds no default query deadline. |
/api/v1/health returns 503 when the required store-health read, ping, or
version probe fails. Most collection read failures also return 503.
/api/v1/status is deliberately diagnostic: it can describe a missing or
unhealthy database with 200; it returns 503 when a required table-summary
read fails after the database was observed to exist.
One-Release Legacy Aliases¶
The previous /api/* dashboard routes remain as direct aliases for one release:
| Canonical route | Temporary legacy alias |
|---|---|
/api/v1/health |
/api/health |
/api/v1/status |
/api/status |
/api/v1/analytics |
/api/analytics |
/api/v1/tables |
/api/tables |
/api/v1/tables/:table |
/api/tables/:table |
/api/v1/web-searches |
/api/web-searches |
/api/v1/sessions |
/api/sessions |
A direct alias invokes the same handler, with the same query handling, response
payload, and status code. It is not an HTTP redirect and does not return a
Location header. New clients must use /api/v1; the aliases are temporary
migration support and are removed after their one-release compatibility window.
/api/v1/capabilities is new and has no /api/capabilities alias.
Static Assets¶
API routes take precedence over static-file handling. Every other GET request
is resolved under the configured monitor static directory:
/servesindex.html.- A path naming a directory serves that directory's
index.html. - A path naming a file serves that file with a MIME type inferred from its extension.
- Missing paths return the JSON
404described above. There is no implicit single-page-app rewrite of an unknown path back to the rootindex.html. - Paths containing
.., and paths whose canonical location escapes the static root, return the JSON403described above.
The server validates at startup that the selected static path is a directory
and contains index.html. --static-dir selects custom built assets; without
an explicit override, packaged assets or the source-tree web/monitor/dist
build are used according to the server's static-directory resolution rules.
Because unmatched paths enter static-file handling, an unknown path under
/api/v1 is not evidence that an API resource exists. In particular, clients
must not probe an inferred /api/v1/sessions/:id path.
Shared MCP Endpoint¶
When backend.bind is an explicit loopback IP, the unified backend also serves
MCP at POST /mcp on this listener. The endpoint accepts one JSON-RPC message
per request using Content-Type: application/json. Clients should send
Accept: application/json, text/event-stream; responses are JSON. Calls after
initialize must include the negotiated MCP-Protocol-Version header.
Notifications return 202 Accepted with an empty body.
GET /mcp returns 405 Method Not Allowed. This endpoint does not allocate
MCP session IDs or expose batch and SSE response modes. It shares the same
repository, caches, request admission, and cancellation behavior as the
backend's private Unix-socket MCP service.
MCP routes do not live under /api/v1, and HTTP clients cannot select a named
backend or launch-directory project scope through this endpoint. Use
moraine run mcp -- --project-only for scoped retrieval.
The endpoint is mounted only for an explicit loopback backend.bind. A
configured auth token may permit non-loopback monitor startup, but /mcp
remains disabled on that listener because HTTP request authentication is not
implemented.