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. |
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. |
Subscribing is a separate POST /sse/subscription naming the session ID from
the connect frame. Its response is the readiness signal: once it answers
200 and lists a context in contexts, every later event for that context
reaches the stream. A client that needs to observe something it is about to
trigger must wait for that response, not merely for the stream to open. A
context the caller may not observe is dropped from contexts rather than
refused, so check the list.
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/:context_id/intents |
Relay descriptor: whether this node relays delegated writes for the context (canAuthorOnBehalf). See delegated authorship. |
| POST | /admin-api/contexts/:context_id/intents |
Execute a member’s signed intent under their warrant; 403 if this node may not relay. See delegated authorship. |
| 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/accounts/:account/seal |
Seal a payload to that account’s root key, as this group sees it. Body { plaintext }, hex, at most 64 KiB and never empty; returns { accountRootEpoch, ephemeralPublicKey, nonce, ciphertext }. Caller and target must both be group members, and a non-member is refused identically to an unknown account. Confidentiality only — the sender key is ephemeral and unauthenticated, so anyone knowing a root public key can produce an envelope for it. |
| 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, including its mode. |
| PUT | /admin-api/groups/:group_id/settings/tee-admission-policy |
Set the group’s TEE admission policy: measurement lists, or signedRelease to admit any signed mero-tee release of the named profiles. Optional mode, "replica" (default) or "relay", picks the role admitted TEEs get and converts those already admitted — see replica or relay. |
| 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. Optional ?context_id=<id>: if this node does not hold the blob, look for it among the context’s peers and download it from one. |
| HEAD | /admin-api/blobs/:blob_id |
Get blob info/metadata (headers only). Optional ?context_id=<id>: see below. |
| DELETE | /admin-api/blobs/:blob_id |
Delete a blob. |
HEAD with ?context_id=
Section titled “HEAD with ?context_id=”Without the parameter, HEAD answers only about blobs this node holds — one
local store read, and a 404 for anything else.
With it, a local miss falls through to the same bounded peer search GET uses,
but stops at the header a holder sends before its first chunk: HEAD never
transfers the blob. Discovery is opt-in for a reason — a search can run to the
node’s 30s discovery deadline, while HEAD otherwise reads as a cheap call.
X-Blob-Source says where the answer came from, and which other headers you get:
X-Blob-Source |
Meaning | Headers |
|---|---|---|
local |
This node holds the blob. | Content-Length, Content-Type, ETag, X-Blob-ID, X-Blob-Hash, X-Blob-MIME-Type |
peer |
Only a context peer holds it. | Content-Length (the size that peer reported, omitted if it reported none), X-Blob-ID |
ETag, Content-Type, X-Blob-Hash and X-Blob-MIME-Type are absent from a
peer answer because every one of them is computed from the bytes — the content
hash from local metadata, the MIME type sniffed from the first chunk — and this
node does not have them. They are omitted rather than guessed. A blob held
neither locally nor by any probed peer is still a 404.
Private blobs need the same signed membership proof on this path as on GET; a
peer answers “not held” to an unauthorized probe, so discovery is no weaker and
no stronger than the download path.
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 an attestation, optionally binding keys and carrying its collateral. | Public |
| POST | /admin-api/tee/registration-attest |
Produce an attestation carrying the registration binding, for fleet registration. | Protected |
| 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). |