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/v1Port 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.
Authentication
Section titled “Authentication”Every route except the ones marked below needs a resolved caller. The gateway looks for one in this order:
- A
nexus_humansession cookie, set by browser login. - An
Authorization: Bearer <token>header carrying a scoped token. - 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.
Tokens
Section titled “Tokens”| 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?}.
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.
Errors
Section titled “Errors”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.
Pagination
Section titled “Pagination”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.
Endpoint index
Section titled “Endpoint index”All paths below are relative to /api/v1.
Health and discovery
Section titled “Health and discovery”| 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.
Metadata
Section titled “Metadata”| 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 |
Messages and notifications
Section titled “Messages and notifications”| 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 |
Threads and topics
Section titled “Threads and topics”| 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 |
Presence and inbox
Section titled “Presence and inbox”| 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 |
Agents
Section titled “Agents”| 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 |
Administration
Section titled “Administration”| 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 |
Sources
Section titled “Sources”| 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 |
Session stream
Section titled “Session stream”| Method | Path | Purpose |
|---|---|---|
GET |
/agent-sessions/:id/events |
Server-sent events for one agent session. Requires ownership or a delegated grant |
Send a message
Section titled “Send a message”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.
Signed source push
Section titled “Signed source push”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:
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.
Outside /api/v1
Section titled “Outside /api/v1”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.
- Error codes for the full failure table
- Configuration for the gateway’s ports, bind, and auth-mode variables
- CLI reference for the same operations from a terminal