Base URL and transports
The daemon serves the same REST + SSE API over two local channels at once:monad status --json prints the address the local daemon is actually using. The port can
be overridden with network.port in config.json or the MONAD_PORT environment
variable.
There is also a no-port mode: monad --stdio speaks JSON-RPC over stdin/stdout and binds
nothing.
Authentication
- Loopback and Unix socket — no token. The socket’s filesystem permissions are its
authentication; the loopback listener additionally validates
OriginandHostso a web page cannot drive the daemon cross-site. - Remote access — off by default. Setting
network.remoteAccess.enabledbinds0.0.0.0and requiresAuthorization: Bearer <token>on every non-loopback request. Plain HTTP sends that token in cleartext: put it behind TLS (reverse proxy, SSH tunnel, or VPN). See runtime.md and SECURITY.md.
The two halves of the surface
Universal methods are declared once in@monad/protocol’s METHOD_TABLE
(packages/protocol/src/rpc/method-table.ts)
and are reachable over both REST and every JSON-RPC transport (WebSocket, Unix socket,
stdio). The RPC params schema and the REST verb + URL are both derived from that one
table, so the transports cannot drift. This is the agent-driving surface: sessions,
agents, tools.approve, clarify.respond, skills.list, commands.list.
HTTP-only endpoints have no JSON-RPC twin: the realtime streams, and the whole
management plane under /v1/settings/* (model, channels, MCP servers, ACP agents, peers,
locale) plus usage, stats, indexer, and init. Those controllers tag themselves
detail.tags: ['http-only'], and a parity test fails any route that is neither in
METHOD_TABLE nor tagged — so no endpoint exists silently.
Realtime
Two planes, deliberately never merged — a token flood must not be able to starve approval delivery. Full rules, resume semantics, and the client state machine: realtime-channels.md.
Standing SSE endpoints send heartbeat comments and support resume through both
Last-Event-ID and ?after=. If a cursor is no longer available the server sends a
replacement snapshot rather than silently skipping state.
Errors
Every 4xx/5xx returns one JSON envelope:code is a machine-readable tag (VALIDATION, NOT_FOUND, INTERNAL, UNAUTHORIZED,
…); retryable says whether repeating the request can succeed; requestId is echoed in
the x-monad-request-id response header so a client can correlate a failure with the
daemon log. details is a bounded string map (at most 16 entries) — never a stack trace.
Retries and concurrency
- Idempotency — mutating requests may carry an
Idempotency-Keyheader. The daemon deduplicates retries within one daemon lifetime; a restart intentionally clears that memory. - Compare-and-swap — shared mutable records (session plans, member bindings) take an expected version so a lost update fails loudly instead of silently overwriting.
Local Scalar API reference
The hosted Mintlify site does not publish the daemon’s OpenAPI document. When developing Monad locally, the daemon can instead serve Scalar at/docs, generated directly from
the routes in that checkout. Scalar includes internal and developer-only routes, so it is
developer-mode only and is not a public API compatibility promise.
Start the source development environment, then enable developer mode:
monad status, normally
http://127.0.0.1:<port>/docs. The raw OpenAPI document is available beside it at
http://127.0.0.1:<port>/docs/json for local inspection and tooling.
Route-authored summaries, descriptions, and tags appear unchanged. The developer-mode
document supplies readable fallbacks for any route that has not authored those fields yet,
so every operation remains navigable while route owners progressively improve its precise
contract text.
Turn developer mode off when finished:
Foreign-protocol adapters
Besides its own API, the daemon can speak protocols other tools already understand:
The OpenAI-compatible endpoint is also what
peer federation uses as its
transport between two daemons you own. Enabling it accepts inbound work: read the
inbound-approval policy (
openaiCompat.approval) before turning it on.
Client libraries
Prefer the packages over hand-rolling HTTP:@monad/client— typed daemon client: Treaty calls, SSE and WebSocket parsing, version checks. Validates every event frame against the protocol schemas before handing it to you.@monad/client-rtk— RTK Query cache layer shared by the Web UI and TUI.@monad/protocol— the schemas and types themselves. Import and derive from these rather than redeclaring shapes; they are the single producer for every wire contract.
Scripting without a client library
monad <command> --json covers most automation without touching HTTP at all — stable exit
codes, NDJSON event streams, stdin via -. See cli.md.