Skip to content

REST API

The gateway puts the daemon’s bus behind HTTP so scripts, services, and dashboards can send and read without a terminal. It is optional: the daemon works without it.

Base URL:

http://127.0.0.1:4100/api/v1

Port 4100 is the default; if it is taken the gateway scans up to 4110. The URL it actually bound is written to ~/.nexus/gateway.json, and nexus gateway status prints it. JSON is camelCase throughout. Writes enqueue daemon commands; reads come from store projections.

Every route except the ones marked below needs a resolved caller. The gateway looks for one in this order:

  1. A nexus_human session cookie, set by browser login.
  2. An Authorization: Bearer <token> header carrying a scoped token.
  3. A local operator, when the gateway is running in local mode — the default when it is bound to loopback with no public URL and no remote flag.

If none resolves, the request is 401 with {"error":{"code":"unauthorized","message":"not logged in"}}.

Tier and scope are checked separately per route, so an authenticated caller can still get 403 for requires admin tier or a missing scope.

Method Path Notes
POST /auth/operator-token Mint a bearer token as the local operator. Local mode only. 201
POST /auth/tokens Issue a scoped token. Admin tier. 201
POST /auth/tokens/refresh Exchange a refresh token for a new one. No auth required. 201
DELETE /auth/tokens/:tokenId Revoke a token. Admin tier

POST /auth/operator-token takes {name, project?, scopes?, ttlMs?, refreshTtlMs?}.

Terminal window
curl -s -X POST http://127.0.0.1:4100/api/v1/auth/operator-token \
-H 'content-type: application/json' \
-d '{"name":"ci-bot"}'

Outside local mode, a mutation authenticated by the nexus_human cookie must also send an x-nexus-csrf header matching the nexus_csrf cookie. Bearer-token calls and signed source pushes are exempt, and so are GET, HEAD, and OPTIONS. A missing or mismatched token is 403 forbidden.

Every failure has the same body:

{ "error": { "code": "not_found", "message": "no route for GET /api/v1/nope" } }

code is a string: bad_request, unauthorized, forbidden, not_found, conflict, unprocessable, internal_error, server_error, or client_error. A path that exists but was called with the wrong method returns 405, not 404. Daemon failures surfaced through the gateway are mapped from numeric codes — see Error codes.

Read routes take limit (default 50, or 100 for notifications and the hook audit), before, and after. The legacy read view also accepts afterRowid. Thread and DM history additionally accept waitMs, which holds the request open until new rows arrive or the window expires.

All paths below are relative to /api/v1.

Method Path Purpose
GET /health Liveness. No auth. 200 with {status,store}, 503 when the store is unreachable
GET /capabilities Protocol version and the surfaces this gateway offers
GET /openapi Generated OpenAPI document for these routes
Method Path Purpose
GET /threads List threads
GET /threads/:name/header Thread header
GET /threads/:name/history Thread history
GET /threads/:name/members Thread members
GET /dms/:name/history DM history with one member
GET /history Cross-surface history for the caller
GET /members Member directory with presence
GET /search Search messages
GET /topics List topics
GET /notifications List notifications
GET /routing-rules List routing rules
GET /projects List projects
GET /whoami Project the authenticated caller’s own identity
GET /messages/:id Read one message
GET /agents/:id Show one durable agent identity
GET /agents/:id/runtimes Runtimes for one identity
GET /runtimes All runtimes
GET /hooks Registered message hooks
GET /hooks/public-key Hook signing public key
GET /hooks/audit Hook invocation audit

GET /whoami takes no parameters — it describes whoever the request authenticated as, and is 401 when nobody did.

Method Path Purpose
GET /messages/:id/metadata Read message metadata
PATCH /messages/:id/metadata Set message metadata
GET /sessions/:id/metadata Read session metadata
PATCH /sessions/:id/metadata Set session metadata
GET /threads/:name/metadata Read thread metadata
PATCH /threads/:name/metadata Set thread metadata
GET /agents/:id/metadata Read agent metadata
PATCH /agents/:id/metadata Set agent metadata
Method Path Purpose
POST /messages Send a DM, thread post, publish, or reply
POST /notify Ingest a source notification. No auth; HMAC-verified
POST /notifications Send one explicitly targeted notification. 201
Method Path Purpose
POST /threads Create a thread. 201
PATCH /threads/:name Rename a thread
DELETE /threads/:name Delete a thread
POST /threads/:name/join Join a thread
POST /threads/:name/leave Leave a thread
POST /threads/:name/archive Archive a thread
POST /threads/:name/members Add a member. 201
DELETE /threads/:name/members/:member Remove a member
POST /topics/:name/subscribe Subscribe to a topic
POST /topics/:name/unsubscribe Unsubscribe
Method Path Purpose
POST /status Set presence and current work
POST /heartbeat Refresh presence
POST /inbox/consume Drain pending messages
POST /inbox/ack Acknowledge drained messages
POST /inbox/ack-threads Acknowledge whole threads
POST /register Register a member. 201
POST /rename Rename the caller
POST /routing-rules Validates the body, then returns 501 — standing route-rule writes are not wired
Method Path Purpose
POST /agents Spawn an agent. 201
DELETE /agents/:id Delete an agent identity
POST /agents/:id/credentials Create a runtime credential. 201
DELETE /agents/:id/credentials/:credentialId Revoke a runtime credential
POST /agents/:id/tier Grant a privilege tier
POST /agents/:id/access Grant delegated session access
DELETE /agents/:id/access/:principal Revoke delegated access
POST /agents/:id/owner Transfer ownership
Method Path Purpose
POST /admin/channel Manage topics and feed routing
POST /admin/route Forward one notification to a name or thread
POST /admin/monitor Read the event feed snapshot
POST /admin/transport/secrets Store an external transport secret
DELETE /admin/transport/secrets/:key Remove one
Method Path Purpose
GET /sources List sources
POST /sources Register a source. 201
GET /sources/:name Show one source, without its token
POST /sources/:name/enable Enable a source
POST /sources/:name/disable Disable a source
POST /sources/:name/rotate Rotate the token
DELETE /sources/:name Remove a source
POST /sources/:name/push Signed source push. No auth; HMAC-verified
Method Path Purpose
GET /agent-sessions/:id/events Server-sent events for one agent session. Requires ownership or a delegated grant
Terminal window
curl -s -X POST http://127.0.0.1:4100/api/v1/messages \
-H 'content-type: application/json' \
-H "authorization: Bearer $NEXUS_TOKEN" \
-d '{
"to": { "verb": "dm", "name": "ben" },
"body": "rebase before you start"
}'

to.verb is dm, post, publish, or reply. A dm target needs name or agentId; post needs thread; publish needs topic; reply needs nothing. The body also accepts optional summary, mention, and idempotencyKey.

POST /sources/:name/push is how an outside system puts an event on the bus without holding a bus identity. It carries no cookie or bearer token. Instead you sign the exact request body with the token nexus source register printed:

Terminal window
raw='{"summary":"Deploy started","body":"web v1.4.2 -> prod","meta":{"service":"web"}}'
ts="$(date +%s%3N)"
sig="sha256=$(printf '%s.%s' "$ts" "$raw" | openssl dgst -sha256 -hmac "$SOURCE_TOKEN" -hex | awk '{print $2}')"
curl -s -X POST http://127.0.0.1:4100/api/v1/sources/github-ci/push \
-H 'content-type: application/json' \
-H "x-nexus-timestamp: $ts" \
-H "x-nexus-signature: $sig" \
-d "$raw"

The signature is sha256=HMAC-SHA256(token, "<timestamp>.<raw body>"). Timestamps are Unix milliseconds and must be within five minutes of the gateway’s clock, so a captured request cannot be replayed later. Full walkthrough in Notifications.

The gateway also serves POST /api/mcp, an authenticated MCP endpoint, alongside the browser console’s own routes. Those are not part of the REST contract above.