Server & API

calimero-server + calimero-server-primitives

5
HTTP surfaces
30+
admin endpoints
Axum
framework
2
auth modes

Purpose

A single Axum HTTP server that binds multiple listeners and mounts all public-facing surfaces: Admin REST API, JSON-RPC 2.0, WebSocket, Server-Sent Events, Prometheus metrics, an optional embedded auth provider (mero-auth), and a static admin dashboard SPA. All business logic is delegated to ContextClient and NodeClient façades.

crates/server

HTTP Surfaces

The server exposes five distinct surface types on the same port. Each is mounted as a separate Axum router and merged into the main application.

Axum Server single port · TLS optional · tower middleware Admin REST API /admin-api/* Protected + public routes Context management Group operations App lifecycle Blob management Identity & health 30+ routes JSON-RPC 2.0 /jsonrpc POST execute method → ContextClient Spec-compliant Batch requests Typed error codes WASM WebSocket /ws upgrade NodeEvent stream Context subscriptions Filtered by context Ping/pong keepalive realtime SSE /sse persistent Persistent sessions Same event stream HTTP/1.1 compatible Auto-reconnect events Prometheus /metrics Text exposition Request latencies Active connections Context counts monitoring
Admin REST
JSON-RPC
WebSocket
SSE
Prometheus

Admin API Endpoints

The Admin REST API provides comprehensive management of contexts, groups, applications, blobs, and node identity. All protected routes require authentication (JWT or proxy).

Contexts
GET /contexts
List all contexts visible to this node
POST /contexts
Create a new context (initializes storage, deploys app)
POST /contexts/:id/join
Join an existing context via invitation
POST /contexts/:id/invite
Generate an invitation (SignedOpenInvitation)
DELETE /contexts/:id
Delete a context and all associated data
GET /contexts/:id
Get context details (app, members, state hash)
GET /contexts/:id/storage
Inspect raw storage contents
GET /contexts/:id/identities
List context identities and their roles
POST /contexts/:id/capabilities/grant
Grant a capability to a member
POST /contexts/:id/capabilities/revoke
Revoke a capability from a member
POST /contexts/:id/sync
Trigger manual sync for a context
Groups
GET /groups
List all groups
POST /groups
Create a new group
GET /groups/:id
Get group info (settings, visibility, member count)
PUT /groups/:id
Update group metadata
DELETE /groups/:id
Delete a group
GET /groups/:id/members
List group members with roles
POST /groups/:id/members
Add a member to the group
DELETE /groups/:id/members/:mid
Remove a member from the group
PUT /groups/:id/members/:mid/roles
Update member roles
PUT /groups/:id/members/:mid/aliases
Set member aliases
PUT /groups/:id/members/:mid/caps
Set member capabilities
GET /groups/:id/contexts
List contexts belonging to this group
POST /groups/:id/invite
Generate group invitation
POST /groups/:id/join
Join a group via invitation
GET /groups/:id/signing-key
Get the group's signing key
POST /groups/:id/upgrade
Propose a group app upgrade
POST /groups/:id/sync
Trigger group governance sync
PUT /groups/:id/visibility
Set group visibility (public/private)
PUT /groups/:id/allowlist
Update the group allowlist
PUT /groups/:id/settings
Update group settings
Applications
POST /applications/install
Install an application from a package registry
POST /applications/install-dev
Install from local path (dev mode, no registry)
GET /applications
List installed applications
GET /applications/:id
Get application details (version, size, contexts)
DELETE /applications/:id
Uninstall an application
Blobs
POST /blobs
Upload a binary blob (content-addressed, returns BlobId)
GET /blobs/:id
Download blob content
GET /blobs/:id/info
Get blob metadata (size, hash, refcount)
GET /blobs
List all blobs
DELETE /blobs/:id
Delete a blob (if refcount = 0)
Packages
GET /packages
List packages in the registry
GET /packages/:id/versions
List all versions of a package
GET /packages/:id/latest
Get the latest version metadata
Identity & Other
POST /identity/context
Generate a new context identity (Ed25519 keypair)
GET /health
Health check (returns 200 if node is running)
GET /is-authed
Check if the current request is authenticated
GET /certificate
Get the node's TLS certificate info
GET /peers
List connected peers with connection info
GET /tee/info
TEE enclave information (if running in TEE)
POST /tee/attest
Request a TEE attestation
POST /tee/verify
Verify a TEE attestation from another node

Authentication

The server supports two authentication modes. The choice is made at startup via configuration.

Proxy Mode default

No in-process JWT handling. The server assumes an external reverse proxy (e.g. nginx, Envoy, Cloudflare Access) terminates TLS and validates credentials before forwarding requests. The server trusts all incoming requests as authenticated.

Suitable for production deployments behind an identity-aware proxy with mTLS or OAuth2 termination.

Embedded Mode mero-auth

Full in-process JWT issuing and verification via the mero-auth crate. Mounts additional routes at /auth for login flows (NEAR wallet, Ethereum wallet, challenge-nonce). Issues JWT tokens on successful authentication.

Protected routes are guarded by an AuthGuardLayer (Tower middleware). Tokens can be passed via:

  • Authorization: Bearer <token> header
  • ?token=<token> query parameter
Client request AuthGuardLayer validate JWT · extract claims or pass-through (proxy mode) Route Handler admin / rpc / ws / sse ContextClient / NodeClient business logic via actor facades /auth endpoints login · challenge · verify · refresh embedded only

Server → Actor Connection

The server holds an AdminState struct that is shared across all route handlers via Axum's state extraction. All business logic flows through the ContextClient and NodeClient façades — the server never accesses storage or actors directly.

pub struct AdminState {
    store: Arc<dyn Database>, // read-only queries (list, get)
    ctx_client: ContextClient, // execute, create, join, governance
    node_client: NodeClient, // peers, blobs, boot
}
AdminState store: Arc<dyn Database> ctx_client: ContextClient node_client: NodeClient Storage (read-only) list contexts, get metadata, query state ContextManager via LazyRecipient<ContextMessage> NodeManager via LazyRecipient<NodeMessage> All mutations flow through actor façades. Server never writes to storage directly.
Request lifecycle example
1

HTTP Request Arrives

Axum receives a POST /admin-api/contexts request. Tower middleware extracts JWT claims (embedded mode) or passes through (proxy mode).

2

Route Handler

The route handler extracts AdminState and request body. Constructs the appropriate parameters for the operation.

3

Façade Call

Calls state.ctx_client.create_context(params).await. This sends a ContextMessage::CreateContext to the ContextManager actor via LazyRecipient.

4

Response

The actor processes the message, returns a Result. The route handler serializes it to JSON and sends the HTTP response.

Route Catalog

Admin API Routes

— Application Management — POST /admin-api/dev/install-application POST /admin-api/dev/install-application-from-url GET /admin-api/dev/applications GET /admin-api/dev/applications/:app_id — Context Management — POST /admin-api/contexts GET /admin-api/contexts GET /admin-api/contexts/:ctx_id DELETE /admin-api/contexts/:ctx_id POST /admin-api/contexts/invite POST /admin-api/contexts/join PUT /admin-api/contexts/:ctx_id/application — Group Management — POST /admin-api/groups GET /admin-api/groups GET /admin-api/groups/:group_id DELETE /admin-api/groups/:group_id POST /admin-api/groups/:group_id/members DELETE /admin-api/groups/:group_id/members GET /admin-api/groups/:group_id/members — Identity & Node — GET /admin-api/identity GET /admin-api/node-info GET /admin-api/peers

Auth Modes

Embedded Auth

Set network.server.embedded_auth = true in config. The server runs the mero-auth JWT service internally. Admin Dashboard SPA is served from /admin/. Frontend assets are baked in at build time via CALIMERO_AUTH_FRONTEND_DIR.

Proxy Auth

Run mero-auth as a separate service behind a reverse proxy (nginx/Traefik). The proxy validates tokens via /auth/validate before forwarding to the node. Node remains auth-unaware.