This page describes the canonical HTTP API used by the Web UI, OtterDesk, and
HTTP automation. The CLI and Python SDK also use internal gRPC interfaces; the
gRPC v2 package name and domain schema labels such as mn.workflow/v1 are
independent of the REST version.
- Reader: integrator, application developer, or maintainer using the public HTTP boundary.
- Outcome: select canonical resources, make conditional or idempotent requests, and monitor accepted work without relying on retired routes.
- Page type: REST and streaming API reference.
- Scope: the canonical
mirrorneuron.rest.v1HTTP and SSE surface. Internal gRPC interfaces, CLI syntax, and domain payload schemas are out of scope. - Maturity: stable canonical v1 surface; removed and unsupported routes are identified explicitly.
- Sources of truth:
mn-apiroute modules, request models, operation implementation, OpenAPI schema, and contract tests. - Validation: audit local links and Markdown syntax, then run the focused
mn-apicontract tests for changed routes and response behavior.
The default local base URL is:
http://localhost:54001/api/v1
GET /health is unauthenticated and deliberately small:
{
"status": "ok",
"api_contract": "mirrorneuron.rest.v1",
"auth": "enabled"
}Clients must verify api_contract. A server that responds on /api/v1 without
that value implements a historical, incompatible contract and must not be used.
All other endpoints require Authorization: Bearer <token> when
MN_API_TOKEN is configured.
/api/v2, historical aliases, /runtime-runs, and WebSocket routes are not
supported and return 404.
- JSON fields use
snake_case; request bodies reject unknown fields. - Collection responses are
{ "items": [], "next_page_token": null }. page_sizedefaults to50and is capped at200. Pass the opaquepage_tokenunchanged to continue. Tokens expire after 24 hours and are bound to the route, principal, filters, and stable sort.- Synchronous creation returns
201 CreatedwithLocation. Accepted Runs and Operations return202 AcceptedwithLocation. Updates return200; a completed deletion returns204 No Content. - Send
Idempotency-Keyon non-idempotent POST requests, especially Run creation, blueprint addition or removal, schedule dispatch, and administrative operations. Replay records last 24 hours; reusing a key for another request returns409 Conflict. - Persistent jobs, schedules, deployments, model registrations, and model
installations return strong
ETagheaders. TheirPATCHandDELETErequests requireIf-Match; missing and stale conditions return428and412, respectively. - Errors use
application/problem+jsonwithtype,title,status,detail,instance,code,request_id, and bounded fielderrorswhen applicable. Work that fails after a202 Acceptedresponse records its terminal failure on the Operation instead of returning a second HTTP error.
| Area | Canonical resources |
|---|---|
| Health and system | /health, /runtime/status, /runtime/health, /runtime/diagnostics, /runtime/resources, /system/summary, /metrics |
| Jobs | /jobs, /jobs/{job_id}, /jobs/{job_id}/bundle, /jobs/{job_id}/data-resets, /jobs/{job_id}/mcp |
| Runs | /jobs/{job_id}/runs, /blueprints/{blueprint_id}/runs, /runs, /runs/{run_id} |
| Run detail | /runs/{run_id}/monitor, /workflow-progress, /logs, /events, /resources, /human-requests, /ui, /artifacts, /outputs, /snapshots, /agent-graph, /export, /observability |
| Bundles and blueprints | /bundles, /blueprints, /blueprints/{blueprint_id}, /additions, /removals, /validations, /runs |
| Schedules and events | /jobs/{job_id}/schedules, /schedules, /schedules/{schedule_id}, /dispatches, /trigger-events |
| Infrastructure | /nodes, /deployments, /models, /model-remotes, /model-proxies, /services, /service-checks |
| Operations | /operations, /operations/{operation_id} |
Durable configured work is a Job. One execution of that definition is a Run.
Run URLs always use the public run_id; runtime_run_id is diagnostic metadata
and is never part of a client URL.
For authored source packages, register an mn.response extension with
enabled: true; the SDK projects it into the generated descriptor's
response_service field. Legacy runtime descriptors may instead declare
mcp_collaboration.enabled. Enabled stable Jobs expose this Streamable HTTP endpoint:
POST /api/v1/jobs/{job_id}/mcp
It uses the same bearer-authentication policy as protected REST routes. The URL
binds the MCP session to one Job; no tool accepts a job_id, so a caller cannot
switch Jobs through tool arguments. Legacy Jobs discover exactly three
read-only tools; response-enabled Jobs discover those three plus ask_job:
| Tool | Result |
|---|---|
get_job_profile() |
Blueprint identity, mission, capabilities, safe configuration, schedule, and lifecycle state. |
get_latest_run() |
Bounded latest-run status, result projection, structured evidence, warnings, and timestamps. |
get_job_context(evidence_limit=50) |
Combined profile, schedule, latest-run summary, warnings, and truncation metadata. |
ask_job(question, conversation_id?, request_id?) |
Fast grounded answer about purpose, status, progress, published results, or missing evidence; never starts a Run. Response-enabled Jobs only. |
Context results use mn.mcp.job_context.v1. The lifecycle projection is one of
never_run, running, idle, paused, scheduled_waiting, or archived.
The endpoint remains readable without an active target Run, including before
the first Run and after completion or failure. If a recent Run's workflow or
result data is temporarily unavailable, the result keeps the stable profile and
adds a warning instead of failing the complete tool call.
Each response is bounded to 256 KiB and 50 evidence records. Projections omit secrets, credentials, environment values, raw logs, host paths, arbitrary files, and unrestricted artifact bodies. The tools cannot start, pause, configure, schedule, approve, or otherwise mutate the Job. Archived Jobs remain readable; deleted, unknown, and non-enabled Jobs return the same sanitized not-found class of error.
ask_job accepts at most 8,000 question characters, a UUID conversation ID,
and a 128-character idempotency ID. It returns mn.mcp.job_answer.v1 with a
12,000-character answer limit, at most 20 citations, and a 64 KiB total limit.
The Job-scoped responder uses one retrieval and one completion, persists bounded
conversation history, and returns a deterministic evidence-only summary with
warnings if the model, RAG, or responder is unavailable. Conversation calls do
not create Runs. No REST, SSE, or UI chat API is provided.
curl -s "http://localhost:54001/api/v1/runs?page_size=50"If next_page_token is not null:
curl -sG http://localhost:54001/api/v1/runs \
--data-urlencode "page_size=50" \
--data-urlencode "page_token=<next-page-token>"Public requests never contain host paths or raw payload paths.
curl -s -X POST http://localhost:54001/api/v1/bundles \
-F "bundle=@./worker-bundle.zip"Use the returned opaque bundle_id:
curl -i -X POST http://localhost:54001/api/v1/jobs \
-H "Content-Type: application/json" \
-H "Idempotency-Key: <uuid>" \
-d '{"bundle_id":"<bundle-id>"}'Catalog jobs may instead use {"blueprint_id":"<blueprint-id>"}.
curl -i -X POST http://localhost:54001/api/v1/jobs/<job-id>/runs \
-H "Content-Type: application/json" \
-H "Idempotency-Key: <uuid>" \
-d '{"inputs":{}}'The response is a pending Run with 202 Accepted. Pause, resume, or cancel it
through one desired-state update:
Catalog-backed Jobs may also send config_overrides. The API rebuilds and
prepares the Job definition with those overrides before starting the Run;
inputs remains reserved for runtime workflow input rather than blueprint
configuration.
curl -s -X PATCH http://localhost:54001/api/v1/runs/<run-id> \
-H "Content-Type: application/json" \
-d '{"desired_state":"paused"}'Invalid state transitions return 409 Conflict.
Prerequisites: run mn-api with its blueprint catalog configured, choose an ID
returned by GET /blueprints, and authenticate when MN_API_TOKEN is set. The
request validates the blueprint bundle, prepares required runtime models and
services, and records the blueprint locally. It does not require a separate
mn blueprint add command.
curl -i -X POST http://localhost:54001/api/v1/blueprints/<blueprint-id>/additions \
-H "Content-Type: application/json" \
-H "Idempotency-Key: <uuid>" \
-d '{"force":false}'The response is 202 Accepted. Its Location header identifies an Operation,
and the body initially resembles:
{
"operation_id": "op-local-<opaque-id>",
"kind": "add_blueprint",
"status": "pending",
"progress": {
"percent": 0,
"stage": "queued",
"label": "Queued",
"detail": "The operation is queued on the local API host."
}
}Poll the Operation until status is completed or failed:
curl -s http://localhost:54001/api/v1/operations/<operation-id>progress.percent is monotonic and bounded from 0 through 100; successful
completion sets it to 100. Addition stages include queued, starting,
resolve_blueprint, validate_blueprint, prepare_runtime,
record_addition, and completed; clients should present the supplied label
and detail and must tolerate new stage names. A completed Operation contains
result.added, the public result.blueprint, its result.addition, and a
sanitized result.runtime_preparation summary.
After completion, GET /blueprints/<blueprint-id> and collection items expose
addition state without the retired installation vocabulary:
{
"id": "<blueprint-id>",
"added": true,
"addition": {
"blueprint_id": "<blueprint-id>",
"status": "added",
"added_at": "<timestamp>",
"revision": "<revision>"
}
}If runtime preparation fails, the Operation has status: "failed" and a
sanitized error with code, title, detail, retryable, optional hint,
and bounded prerequisite errors. MN_BLUEPRINT_ADD_FAILED means a required
runtime prerequisite was not ready; correct the reported prerequisite and send
a new addition request with a new idempotency key.
Warning:
{"force":true}passes a compatibility override into runtime prerequisite preparation. Use it only after reviewing the failed check; it can admit a configuration that the normal compatibility path rejects.
Warning: removal archives the local addition record and, by default, removes blueprint-owned runtime resources, including cleanup-owned files. Deleted runtime data is not restored by adding the blueprint again. Run a dry run first or set
keep_resourceswhen those resources must remain.
Preview removal without changing state:
curl -i -X POST http://localhost:54001/api/v1/blueprints/<blueprint-id>/removals \
-H "Content-Type: application/json" \
-H "Idempotency-Key: <uuid>" \
-d '{"dry_run":true}'Then submit the intended policy with a new idempotency key. This example keeps runtime resources and model images:
curl -i -X POST http://localhost:54001/api/v1/blueprints/<blueprint-id>/removals \
-H "Content-Type: application/json" \
-H "Idempotency-Key: <uuid>" \
-d '{"keep_resources":true,"keep_models":true}'The request fields are:
| Field | Default | Effect |
|---|---|---|
keep_resources |
false |
Preserve blueprint-owned runtime resources instead of cleaning them up. |
keep_models |
false |
Explicitly retain model images after releasing this blueprint's ownership. |
remove_models |
false |
Remove model images that become orphaned; cannot be combined with keep_models. |
dry_run |
false |
Plan the archive, resource cleanup, and model changes without applying them. |
Removal is also asynchronous. Poll its Operation or follow the Operation SSE
stream, then verify that the blueprint projection reports added: false and
addition.status: "not_added". Re-adding can recreate runtime prerequisites,
but it cannot recover resources or data deleted by removal.
The former GET /blueprints/<blueprint-id>/installation,
PUT /blueprints/<blueprint-id>/installation, and
DELETE /blueprints/<blueprint-id>/installation routes are removed and return
404; clients must migrate to additions and removals.
GET /api/v1/models includes all catalog models and discovered installations
by default. Its paginated items carry default: true for the configured
default/fallback chain. Use ?installed_only=true for installed models only;
GET /api/v1/models/<model-id> retains the individual detail view.
PUT /api/v1/models/default/installation with JSON body {} eagerly prepares
the first compatible configured default on a local or cluster node. It uses
catalog overrides and fallbacks without changing the default policy. The
existing request options are backend (default auto), optional context_size,
and force (default false). DMR options are rejected for provider defaults.
Send Idempotency-Key for safe replay. The response is 202 with an Operation
Location; poll or follow its SSE stream for completion and sanitized failures.
Explicit model installation uses the same background SDK preparation boundary.
The only realtime HTTP surfaces are authenticated, resumable SSE:
GET /runs/{run_id}/events/stream
GET /operations/{operation_id}/events/stream
Every event uses this envelope:
{
"id": "42",
"type": "run.snapshot",
"occurred_at": "2026-08-13T17:00:00Z",
"resource": "/api/v1/runs/<run-id>",
"data": {}
}Event IDs increase monotonically. On reconnect, send the last applied value as
Last-Event-ID. The server replays available later events, sends heartbeat
comments while idle, releases stream resources on disconnect, and closes after
a terminal event. Keep ordinary snapshot and history GETs as the recovery
source of truth.
API-owned addition and removal Operations emit operation.accepted,
operation.progress, and one terminal operation.completed or
operation.failed event. Each event's data is the current Operation snapshot,
including its progress and terminal result or error.
curl -N http://localhost:54001/api/v1/runs/<run-id>/events/stream \
-H "Authorization: Bearer <token>" \
-H "Last-Event-ID: <last-event-id>"Artifact and output lists contain download URLs beneath the public Run. They do not expose host filesystem paths and there is no server-side “reveal” action. Browser clients download or open the artifact; native clients may reveal the downloaded local file using their own platform integration.
Use mn job for durable definitions and mn run for executions. The Python
SDK exposes typed pages, revision-aware conditional updates, and idempotent
create/dispatch methods while keeping HTTP-specific headers and Problem Details
adaptation inside mn-api.
Blueprint lifecycle terminology also matches the CLI: the REST API creates blueprint additions and removals; it does not expose a blueprint installation resource. Installation remains valid terminology for model resources.
Common operator commands include:
mn runtime status
mn node list
mn job list
mn run listSee CLI Reference for the complete command surface.