Gateway API overview
The Axum gateway architecture, body/timeout limits, and how REST, SSE, and WebSocket transports fit together.
The Revka gateway is an Axum-based HTTP server that exposes the entire agent runtime over a structured REST API plus WebSocket and Server-Sent Events transports. The same process serves the embedded React dashboard, every /api/* route, the realtime streams, and a built-in MCP server reverse-proxied for same-origin browser access. This page is the architectural map: how the three transports fit together, the shared body and timeout limits every route inherits, the per-route overrides, and the four authentication surfaces the gateway exposes.
Read this first if you are building a client against the gateway, sizing a reverse proxy, or deciding which transport to use for a given feature. For the request-by-request reference of each route group, follow the links in Where to go next. To start the server itself, see revka gateway, daemon & service.
Architecture
Section titled “Architecture”revka gateway starts just the HTTP/WebSocket gateway; revka daemon starts the same gateway plus channels, the heartbeat, and the cron scheduler. Both expose an identical HTTP surface, organized into five layers:
| Layer | Path prefix | Transport | Auth |
|---|---|---|---|
| Embedded dashboard | /, /_app/* | HTTP (static) | None (SPA shell) |
| REST API | /api/* | HTTP (JSON) | Bearer token |
| Realtime streams | /api/events, /api/daemon/logs | SSE | Bearer token |
| WebSocket transports | /ws/* | WebSocket | Bearer token |
| Public ingress | /health, /metrics, /pair, /webhook, /whatsapp, /track/c/*, … | HTTP | None or per-endpoint signing |
The dashboard is served same-origin with the API, so the browser never crosses a CORS boundary. A separate in-process MCP server binds an ephemeral local port; the gateway reverse-proxies it under /api/mcp/* (and /ws/mcp/events) so browser clients can reach MCP tools without leaving the origin. External MCP clients read the in-process server’s address directly from ~/.revka/mcp.json.
Transport map
Section titled “Transport map”The gateway speaks three transports. Pick the one that matches the interaction shape:
- REST for request/response operations — config, status, CRUD on cron jobs, agents, skills, teams, workflows, and memory assets.
- SSE for one-way broadcast streams the server pushes to all subscribers — observability events and daemon logs.
- WebSocket for bidirectional, per-connection sessions — interactive agent chat, the PTY terminal, live canvas updates, MCP progress events, and node registration.
REST (/api/*)
Section titled “REST (/api/*)”Every /api/* route is JSON request/response and protected by bearer-token authentication. Representative endpoint:
GET /api/statusAuthorization: Bearer <token>{ "provider": "openrouter", "model": "anthropic/claude-sonnet-4", "uptime_seconds": 3600, "gateway_port": 8080, "memory_backend": "kumiho", "paired": true, "channels": {}, "health": {}}Server-Sent Events
Section titled “Server-Sent Events”Two SSE endpoints push server-originated streams to dashboard clients. Both require a bearer token when pairing is enabled and expect Accept: text/event-stream.
| Endpoint | Streams |
|---|---|
GET /api/events | Observability broadcast: llm_request, tool_call_start, tool_call, agent_start, agent_end, error, and channel_event |
GET /api/daemon/logs | A live tail of the daemon stderr log — an initial ~64 KB burst, then 500 ms polling |
GET /api/eventsAuthorization: Bearer <token>Accept: text/event-streamSSE is broadcast and observability-oriented; it is not a per-session conversation channel. Use /ws/chat for that.
WebSocket (/ws/*)
Section titled “WebSocket (/ws/*)”WebSocket endpoints carry bidirectional, stateful sessions. Browsers cannot set an Authorization header on new WebSocket(), so the gateway accepts the bearer token through three fallbacks, in precedence order:
Authorization: Bearer <token>headerSec-WebSocket-Protocol: bearer.<token>subprotocol?token=<token>query parameter
| Endpoint | Purpose | Subprotocol |
|---|---|---|
/ws/chat | Interactive agent chat — streaming chunks, tool events, steering, stop, attachments | revka.v1 |
/ws/terminal | PTY terminal that can launch a shell or an AI coding CLI | revka.v1 |
/ws/canvas/{id} | Subscribe to live frames pushed to a named canvas | revka.v1 |
/ws/mcp/events | Proxy of the in-process MCP server’s session-events stream (Code tab) | revka.v1 |
/ws/nodes | External nodes register capabilities that become agent tools | revka.nodes.v1 |
ws://host:port/ws/chat?session_id=<uuid>&name=My+Session&token=<bearer>The PTY terminal is disabled on Android. Full frame-by-frame protocols for each WebSocket endpoint live in Realtime: WebSocket, SSE & Live Canvas.
Body and timeout limits
Section titled “Body and timeout limits”Two limits apply to every route unless a route opts out: a request-body size cap and a request timeout.
64 KiB body cap
Section titled “64 KiB body cap”The gateway applies a global request-body limit of 65,536 bytes (64 KiB). A request whose body exceeds this is rejected before it reaches the handler. The cap is deliberately small because the vast majority of API calls are compact JSON; routes that legitimately carry larger payloads declare their own higher limit.
Per-route overrides
Section titled “Per-route overrides”A handful of endpoints raise the cap to fit their payload. These are the documented overrides:
| Route | Limit | Why |
|---|---|---|
PUT /api/config | 1 MiB | A full config.toml body |
POST / PUT /api/workflows, PUT /api/workflows/{*kref} | 16 MiB | Real workflow YAML can exceed 64 KiB |
POST /api/sessions/{id}/attachments | 25 MiB | Per-file image/document upload |
POST /api/skins/import | 25 MiB | UI skin ZIP upload |
POST /api/agents/avatar, POST /api/teams/avatar | 5 MiB | Avatar image upload |
PUT /api/assets/artifacts/content | 2 MiB | Artifact content edit |
GET /api/artifact-body | 256 MiB | Serving raw artifact bytes (response-side) |
Request timeout
Section titled “Request timeout”Every request is also bounded by a timeout, primarily to defeat slow-loris connections. The default is 30 seconds, set by REQUEST_TIMEOUT_SECS.
# Raise the per-request timeout to 5 minutes for long agentic callsREVKA_GATEWAY_TIMEOUT_SECS=300 revka gateway| Variable | Default | Meaning |
|---|---|---|
REVKA_GATEWAY_TIMEOUT_SECS | 30 | Overrides the global per-request timeout, in seconds. |
A few endpoints carry their own longer deadlines independent of this default — for example GET /api/memory/graph allows 60 seconds for large graphs, and POST /api/nodes/{node_id}/invoke uses a 30 second hard limit per invocation.
Authentication surfaces
Section titled “Authentication surfaces”The gateway has four distinct auth surfaces. Knowing which one a route uses is the key to integrating against it.
Bearer token (pairing)
Section titled “Bearer token (pairing)”The primary surface. Every /api/* route, the SSE streams, and the WebSocket endpoints require a bearer token minted by the one-time pairing flow:
Authorization: Bearer <token>A client exchanges a pairing code for a token, the gateway stores only a hash, and the token is shown exactly once. Full details — codes, the SQLite device registry, token rotation, and rate limiting — are in Pairing & authentication.
Service token (internal)
Section titled “Service token (internal)”A small set of mutation endpoints are reachable only with an internal service token, not a user bearer token, because they touch privileged state. The token is auto-generated at gateway startup and persisted to <state_dir>/service-token (mode 0600 on POSIX). It is passed in a dedicated header:
X-Revka-Service-Token: <service-token>Examples include POST /api/cost/usage (writes the budget ledger) and POST /api/auth/profiles/{id}/resolve (the only path that decrypts a stored credential). The service token is also the HMAC signing key for signed workspace asset URLs.
Localhost-only admin
Section titled “Localhost-only admin”Administrative endpoints are bound to the loopback check: requests from a non-local IP receive 403. No bearer token is required because reachability is restricted to the host itself.
| Endpoint | Used by |
|---|---|
POST /admin/shutdown | revka stop |
GET /admin/paircode, POST /admin/paircode/new | revka gateway get-paircode |
POST /api/channel-events | Operator and local services publishing events |
Public and signed ingress
Section titled “Public and signed ingress”Some endpoints are intentionally unauthenticated because the caller cannot hold a bearer token — load balancers, webhook providers, or email recipients clicking a link.
- Liveness and metrics:
GET /healthreturns200with pairing status and a component snapshot and leaks no secrets;GET /metricsexposes Prometheus text format when[observability] backend = "prometheus". - Webhook ingress:
POST /webhook,/whatsapp,/linq,/wati,/nextcloud-talk,/webhook/gmail. These verify a provider HMAC or shared secret rather than a bearer token, and are per-IP rate limited. - Signed URLs:
GET /track/c/{encoded}(email click tracking) andGET /workspace/{*path}?exp=&sig=(HMAC-signed, time-limited workspace assets) carry their own signature instead of anAuthorizationheader.
WebAuthn/FIDO2 hardware-key authentication is available as an optional, feature-gated surface (/api/webauthn/*, requires the webauthn compile feature). See TLS, rate limiting, WebAuthn & static serving.
Putting it together
Section titled “Putting it together”A typical dashboard or client session uses all three transports at once:
-
Pair once to obtain a bearer token, then probe
GET /health(no auth) orGET /api/status(bearer) to confirm the gateway is up. -
Drive operations over REST — read and write config, manage cron jobs and agents, query the memory graph — each call carrying
Authorization: Bearer <token>and staying under the 64 KiB cap (or the per-route override). -
Subscribe to
/api/eventsover SSE for live observability, cost, and approval events. -
Open
/ws/chatfor an interactive turn, passing the token via therevka.v1subprotocol or?token=query parameter, and stream chunks until thedoneframe.