Admin & JSON-RPC API Reference
The node’s HTTP server (default bind /ip4/127.0.0.1/tcp/2528 and
/ip6/::1/tcp/2528) exposes four
surfaces: a REST-style admin API, a JSON-RPC endpoint for app
execution, a WebSocket stream, and a Server-Sent Events stream. Every
path below is taken directly from the axum routers in the server crate.
Base paths
Section titled “Base paths”| Surface | Base path | Enabled by |
|---|---|---|
| Admin API | /admin-api |
[server.admin] enabled = true |
| JSON-RPC | /jsonrpc |
[server.jsonrpc] enabled = true |
| WebSocket | /ws |
[server.websocket] enabled = true |
| Server-Sent Events | /sse |
[server.sse] enabled = true |
| Admin dashboard UI | /admin-dashboard |
[server.admin] enabled = true |
Every request body on these surfaces is a closed set. A body carrying a field the node does not declare is a 400 at deserialization, never a silent drop, so a client built against a different shape fails loudly instead of installing, joining, or upgrading something it did not name. Query strings stay lenient.
Choosing an API surface
Section titled “Choosing an API surface”All four surfaces are mounted on a single HTTP server and wrapped by the same optional auth layer — when embedded auth is on, every one of them is guarded identically; when it is off, all four are open. They differ in shape, not in how they authenticate:
| Surface | Path | Shape | Use it for |
|---|---|---|---|
| Admin REST | /admin-api |
Request/response | Node management: install apps, create contexts and groups, manage members and aliases, inspect state. |
| JSON-RPC | /jsonrpc |
Request/response | One-shot app calls. execute (run a method) and sync_status. |
| WebSocket | /ws |
Bidirectional, long-lived | App calls plus live updates over one connection: execute, subscribe, unsubscribe. |
| SSE | /sse |
Server→client stream | A read-only event feed when you only need to receive events (no custom request headers needed). |
Two practical notes:
- There is one execute, reached two ways. The
executeover JSON-RPC and theexecuteover WebSocket resolve to the same execution kernel — same executor resolution, same runtime invocation, same result envelope. Pick JSON-RPC for a single stateless call; pick WebSocket when you also want to subscribe to events on the same connection. - Query vs. mutate is not a flag you set. Whether an
executecall only reads state or commits a change is decided by the target method’s own#[app::view]declaration in the app, not by the transport or any request field. The sameexecuterequest shape covers both.
Authentication
Section titled “Authentication”Admin routes split into protected and public sets. The protected set is
wrapped in an auth guard whose behavior depends on [server] auth_mode:
proxy(default) — the node expects an external auth proxy in front of it.embedded— the bundledmero-authservice enforces auth in-process.
The public routes below carry no auth guard and are always reachable when the admin API is enabled.
| Method | Path | Purpose |
|---|---|---|
| GET | /admin-api/health |
Liveness/health check. |
| GET | /admin-api/is-authed |
Reports whether the caller is authenticated. |
| GET | /admin-api/certificate |
Returns the node’s TLS/admin certificate. |
CORS and browser / Tauri clients
Section titled “CORS and browser / Tauri clients”The server applies one CORS layer to every mounted route. It allows Any
origin, Any request header, the methods GET/POST/PUT/PATCH/DELETE/OPTIONS,
and sets allow_private_network(true) so requests from a public-origin page to
a loopback node pass the browser’s private-network preflight.
Because the response uses Any origin, allow_credentials is intentionally
not set (the two are incompatible per the CORS spec, so enabling it would be a
no-op for browsers). Cookie/credentialed auth therefore does not work against
the default configuration; pass tokens via the Authorization header instead.
Cross-origin clients (browser SPAs, Tauri webviews) can only read response headers that the server explicitly exposes. The layer exposes exactly three:
| Exposed header | Use |
|---|---|
x-auth-error |
Signals the auth-failure reason. A 401 from an expired-but-refreshable token carries X-Auth-Error: token_expired. |
x-auth-user |
The authenticated user/key. |
x-auth-permissions |
The caller’s resolved permissions. |
To implement transparent refresh-on-401, read X-Auth-Error on a 401: if it is
token_expired, refresh the access token and retry; otherwise treat it as a
hard auth failure. If these headers are not exposed (e.g. behind a proxy that
strips them), every access-token expiry surfaces as a hard logout instead.
JSON-RPC
Section titled “JSON-RPC”A single endpoint accepts JSON-RPC 2.0 requests. The method field selects the
operation; params carries its arguments.
| Method | Path | Purpose |
|---|---|---|
| POST | /jsonrpc |
JSON-RPC 2.0 request dispatch. |
Supported method values:
method |
Purpose |
|---|---|
execute |
Execute an application method against a context (mutating or query). Params: contextId, method, argsJson, optional substitute aliases. |
sync_status |
Report sync status. |
{ "jsonrpc": "2.0", "id": 1, "method": "execute", "params": { "contextId": "<context-id>", "method": "set", "argsJson": { "key": "a", "value": "b" }, "substitute": [] }}WebSocket
Section titled “WebSocket”| Method | Path | Purpose |
|---|---|---|
| GET | /ws |
WebSocket upgrade. Carries execute, subscribe, and unsubscribe messages over the socket. |
When embedded auth is enabled, the auth guard runs at the HTTP upgrade request, before the socket is established — an unauthenticated upgrade is rejected with a 401 and never becomes a WebSocket. The guard resolves the token in this order:
- An
Authorization: Bearer <jwt>header. If present, it is validated exclusively — even an invalid token here is rejected rather than retried. - Only if no
Authorizationheader is present, a?token=<jwt>query parameter. This fallback exists because the browserWebSocketandEventSourceAPIs cannot set custom headers.
If the Authorization header is present, the ?token= query parameter is
ignored entirely.
Server-Sent Events
Section titled “Server-Sent Events”| Method | Path | Purpose |
|---|---|---|
| GET | /sse |
Open an SSE event stream. |
| GET | /sse/session/:session_id |
Fetch/attach to an existing SSE session. |
| POST | /sse/subscription |
Manage subscriptions for an SSE session. |
SSE durability and reconnection
Section titled “SSE durability and reconnection”The SSE stream uses a skip-on-disconnect delivery model: there is no replay buffer. Sessions persist (subscriptions and a monotonic event counter survive a reconnect), but any event emitted while a client is disconnected is lost and never redelivered. Event IDs are sequential, so a gap in the IDs a client receives is the signal that events were missed.
On connect the response carries two headers for the client to track its session:
| Response header | Meaning |
|---|---|
X-SSE-Session-ID |
The session ID assigned to (or resumed by) this stream. |
X-SSE-Reconnect |
true if this connection resumed an existing session, false for a fresh one. |
To reconnect, the client sends the Last-Event-ID header. Event IDs have the
format {session_id}-{n}; the server parses the session_id prefix to restore
the session. Sessions expire after 24 hours of inactivity, after which a
reconnect attempt starts a fresh session. The stream also advertises a retry
hint of 3000ms in its initial event.
Admin API — Applications
Section titled “Admin API — Applications”| Method | Path | Purpose |
|---|---|---|
| POST | /admin-api/install-application |
Install package@version from the node’s configured registry. |
| POST | /admin-api/install-dev-application |
Install a local dev application. |
| GET | /admin-api/applications |
List installed applications. |
| GET | /admin-api/applications/:application_id |
Get one application. |
| GET | /admin-api/applications/:application_id/abi |
Get the application’s embedded WASM ABI manifest (optional ?service_name=). |
| DELETE | /admin-api/applications/:application_id |
Uninstall an application. |
| GET | /admin-api/applications/:application_id/versions |
List versions of an installed application. |
Packages
Section titled “Packages”| Method | Path | Purpose |
|---|---|---|
| GET | /admin-api/packages |
List known packages. |
| GET | /admin-api/packages/:package/versions |
List versions of a package. |
| GET | /admin-api/packages/:package/latest |
Get the latest version of a package. |
Admin API — Contexts
Section titled “Admin API — Contexts”| Method | Path | Purpose |
|---|---|---|
| GET | /admin-api/contexts |
List context IDs. |
| POST | /admin-api/contexts |
Create a context. |
| GET | /admin-api/contexts/:context_id |
Get a context. |
| DELETE | /admin-api/contexts/:context_id |
Delete a context. |
| POST | /admin-api/contexts/:context_id/application |
Update the context’s application. |
| POST | /admin-api/contexts/:context_id/resync |
Force a resync of the context. |
| GET | /admin-api/contexts/for-application/:application_id |
List contexts for an application. |
| GET | /admin-api/contexts/with-executors/for-application/:application_id |
List contexts with their executors for an application. |
| GET | /admin-api/contexts/:context_id/storage |
Get context storage info. |
| GET | /admin-api/contexts/:context_id/identities |
List identities in the context. |
| GET | /admin-api/contexts/:context_id/identities-owned |
List identities the node owns in the context. |
| GET | /admin-api/contexts/:context_id/group |
Get the context’s group. |
| POST | /admin-api/contexts/:context_id/join |
Join a context. |
| POST | /admin-api/contexts/:context_id/leave |
Leave a context. |
| POST | /admin-api/contexts/sync |
Trigger sync across all contexts. |
| POST | /admin-api/contexts/sync/:context_id |
Trigger sync for one context. |
Admin API — Groups
Section titled “Admin API — Groups”| Method | Path | Purpose |
|---|---|---|
| POST | /admin-api/groups |
Create a group. |
| GET | /admin-api/groups/:group_id |
Get group info. |
| PATCH | /admin-api/groups/:group_id |
Update group settings. |
| DELETE | /admin-api/groups/:group_id |
Delete a group. |
| GET | /admin-api/groups/:group_id/contexts |
List contexts in a group. |
| POST | /admin-api/groups/:group_id/reparent |
Re-parent a group. |
| GET | /admin-api/groups/:group_id/subgroups |
List subgroups. |
| GET | /admin-api/groups/:group_id/members |
List group members. |
| POST | /admin-api/groups/:group_id/members |
Add group members, each named by account, or by key written key:<64-hex>. Every id is hex, so the tag is what tells the two apart — an untagged key is read as an account. |
| POST | /admin-api/groups/:group_id/members/remove |
Remove group members. |
| POST | /admin-api/groups/:group_id/leave |
Leave a group. |
| PUT | /admin-api/groups/:group_id/members/:identity/role |
Update a member’s role. |
| GET | /admin-api/groups/:group_id/metadata |
Get group metadata. |
| PUT | /admin-api/groups/:group_id/metadata |
Set group metadata. |
| GET | /admin-api/groups/:group_id/members/:identity/metadata |
Get member metadata. |
| PUT | /admin-api/groups/:group_id/members/:identity/metadata |
Set member metadata. |
| GET | /admin-api/groups/:group_id/contexts/:context_id/metadata |
Get context-in-group metadata. |
| PUT | /admin-api/groups/:group_id/contexts/:context_id/metadata |
Set context-in-group metadata. |
| POST | /admin-api/groups/:group_id/contexts/:context_id/remove |
Detach a context from a group. |
| POST | /admin-api/groups/:group_id/upgrade |
Upgrade a group. |
| GET | /admin-api/groups/:group_id/upgrade/status |
Get group upgrade status. |
| POST | /admin-api/groups/:group_id/upgrade/retry |
Retry a group upgrade. |
| GET | /admin-api/groups/:namespace_id/cascade-status |
Get cascade status (by namespace). |
| GET | /admin-api/groups/:namespace_id/migration-status |
Get migration status (by namespace). |
| POST | /admin-api/groups/:namespace_id/migration/abort |
Abort a migration (by namespace). |
| POST | /admin-api/groups/:group_id/issue-ownership-proof |
Issue a group ownership proof. |
| POST | /admin-api/groups/:group_id/issue-namespace-ownership-proof |
Issue a namespace ownership proof. |
| POST | /admin-api/groups/:group_id/sync |
Sync a group. |
| POST | /admin-api/groups/:group_id/join-via-inheritance |
Join a subgroup via inheritance. |
| GET | /admin-api/groups/:group_id/members/:identity/capabilities |
Get a member’s capabilities. |
| PUT | /admin-api/groups/:group_id/members/:identity/capabilities |
Set a member’s capabilities. |
| PUT | /admin-api/groups/:group_id/members/:identity/auto-follow |
Set a member’s auto-follow flag. |
| PUT | /admin-api/groups/:group_id/settings/default-capabilities |
Set group default capabilities. |
| GET | /admin-api/groups/:group_id/settings/tee-admission-policy |
Get the group’s TEE admission policy. |
| PUT | /admin-api/groups/:group_id/settings/tee-admission-policy |
Set the group’s TEE admission policy. |
| PUT | /admin-api/groups/:group_id/settings/subgroup-visibility |
Set subgroup visibility. |
| POST | /admin-api/groups/:group_id/invite |
Create a group invitation (legacy). |
| POST | /admin-api/groups/join |
Join a group (legacy). |
Admin API — Namespaces
Section titled “Admin API — Namespaces”| Method | Path | Purpose |
|---|---|---|
| GET | /admin-api/namespaces |
List namespaces. |
| POST | /admin-api/namespaces |
Create a namespace. |
| GET | /admin-api/namespaces/:namespace_id |
Get a namespace. |
| DELETE | /admin-api/namespaces/:namespace_id |
Delete a namespace. |
| POST | /admin-api/namespaces/:namespace_id/invite |
Invite into a namespace. Body may name admitters and admitterAddrs — see below. |
| POST | /admin-api/namespaces/:namespace_id/join |
Join a namespace. |
| POST | /admin-api/namespaces/:namespace_id/admit |
Publish a join signed by a keyholder that has no node of its own. See Direct Admission. |
| POST | /admin-api/namespaces/:namespace_id/leave |
Leave a namespace. |
| GET | /admin-api/namespaces/for-application/:application_id |
List namespaces for an application. |
Invitation body: admitters and addresses
Section titled “Invitation body: admitters and addresses”Both /groups/:group_id/invite and /namespaces/:namespace_id/invite accept the
same body. Every field is optional.
{ "expirationTimestamp": 3600, "recursive": false, "admitters": ["8f2c…e1", "b04a…77"], "admitterAddrs": [ "/ip4/203.0.113.7/tcp/2528/p2p/12D3KooWExample" ]}| field | meaning |
|---|---|
expirationTimestamp |
Validity in seconds, clamped to 24 hours. Not an absolute time despite the name. |
recursive |
Also mint invitations for the namespace’s child groups. |
admitters |
Accounts allowed to admit a claim, 64 hex each. Signed into the invitation. Omit and the node fills in the group’s admins plus its TEE nodes; it refuses to mint if that set is empty. |
admitterAddrs |
libp2p multiaddrs for those admitters, each including its /p2p/<peer-id> suffix. Not signed. Omit and the node fills in what it knows. |
The peer id has to be in the address: a joiner cannot resolve an account to a peer without governance state, which is precisely what it has not synced yet.
Supplied addresses are used as given and are not merged with the node’s own view — a caller naming one is usually correcting that view, not extending it.
Because they are unsigned, a wrong address costs a failed dial and nothing more:
libp2p authenticates the peer id on connect, and the signed admitters list still
decides who may answer. Direct Admission covers the
split.
Admin API — Blobs
Section titled “Admin API — Blobs”| Method | Path | Purpose |
|---|---|---|
| PUT | /admin-api/blobs |
Upload a blob. |
| GET | /admin-api/blobs |
List blobs. |
| GET | /admin-api/blobs/:blob_id |
Download a blob. |
| HEAD | /admin-api/blobs/:blob_id |
Get blob info/metadata (headers only). |
| DELETE | /admin-api/blobs/:blob_id |
Delete a blob. |
Admin API — TEE
Section titled “Admin API — TEE”| Method | Path | Purpose | Auth |
|---|---|---|---|
| GET | /admin-api/tee/info |
TEE info. | Public |
| POST | /admin-api/tee/attest |
Produce/verify an attestation. | Public |
| POST | /admin-api/tee/verify-quote |
Verify a TEE quote. | Public |
| POST | /admin-api/tee/fleet-join |
Join a TEE fleet. | Protected |
Admin API — Identity & Alias
Section titled “Admin API — Identity & Alias”| Method | Path | Purpose |
|---|---|---|
| GET | /admin-api/identity |
Get this node’s account and device identity. Replaced the per-namespace route: identity is node-level, one root to one account to one device. |
| POST | /admin-api/identity/context |
Generate a new context identity (keypair). |
| POST | /admin-api/alias/create/context |
Create an alias for a context. |
| POST | /admin-api/alias/create/application |
Create an alias for an application. |
| POST | /admin-api/alias/create/device |
Create an alias for a device. |
| POST | /admin-api/alias/lookup/context/:name |
Resolve a context alias. |
| POST | /admin-api/alias/lookup/application/:name |
Resolve an application alias. |
| POST | /admin-api/alias/lookup/device/:name |
Resolve a device alias. |
| POST | /admin-api/alias/delete/context/:name |
Delete a context alias. |
| POST | /admin-api/alias/delete/application/:name |
Delete an application alias. |
| POST | /admin-api/alias/delete/device/:name |
Delete a device alias. |
| GET | /admin-api/alias/list/context |
List context aliases. |
| GET | /admin-api/alias/list/application |
List application aliases. |
| GET | /admin-api/alias/list/device |
List device aliases. |
Admin API — Network & Usage
Section titled “Admin API — Network & Usage”| Method | Path | Purpose |
|---|---|---|
| GET | /admin-api/network/status |
libp2p connectivity snapshot (relays, rendezvous, DCUtR, AutoNAT). |
| GET | /admin-api/peers |
Connected peers count. |
| GET | /admin-api/usage |
Per-namespace usage (counts and on-disk bytes). |