← Documentation index Architecture guide › HTTP API

Metnos

Web chat and HTTP API
One boundary for conversation, administration, and resumable state.

metnos_http_server serves the web chat, turn API, and Settings area. Its default is 127.0.0.1:8770; an installation may change both address and port. The remote-executor protocol on 8765 and browser service on 8771 are separate components with their own authority and routes.

Contents

  1. A natural-language example
  2. Server composition
  3. Main routes
  4. Identity and authorisation
  5. Sessions and per-user isolation
  6. Direct and resumable turns
  7. HTML, JSON, and caching
  8. Settings and localisation
  9. Network and capacity boundaries
  10. Verification and canonical sources

1. A natural-language example

Ask Metnos with a request such as:

Show me where I can inspect recent turns and change the model configuration.

The answer should begin with the route visible in the chat: Settings > Activity > Turns and Settings > System > Models. If the question came from Telegram, the answer must state that Settings opens in the web chat. Only a person asking for a technical integration then needs the HTTP paths /admin/turns and /admin/virt.

2. Server composition

The make_app factory creates one aiohttp application with a 50 MiB request-body limit and mounts three route registries:

ModuleResponsibility
http_routes_agent.pyChat, discovery, turns, sessions, dialogs, attachments, web pairing, and OAuth callback.
http_routes_admin.pySettings, users, models, services, devices, scheduler, Safety, and executor evolution.
http_routes_stack.pyCombined readiness for the server, catalog, and browser service.

The same middleware order applies to every route: the first layer classifies the caller; the second places that user's language in the request context. The language context is copied into the turn pool as well, so concurrent requests do not share a global language variable.

2.1 Adjacent services

3. Main routes

3.1 Entry and discovery

Method and pathFunctionAccess
GET /Web chat; an anonymous caller is redirected to administrator login.User or administrator; initial anonymous entry is admitted for the redirect.
GET /agent/healthLiveness, API version, product version, and uptime.Anonymous.
GET /.well-known/metnos.jsonNode descriptor and pairing URL; no administrative key or fingerprint is disclosed.Anonymous.
POST /agent/registerConsumes a remote executor's one-time token.Anonymous with a valid token.
GET /pair/{token}Consumes a web pairing token and sets the paired-device cookie.Anonymous with a valid token.
GET /oauth/callbackCompletes an OAuth flow using short-lived state issued by Metnos.Public for redirect purposes; state is the boundary.

The PWA manifest, service worker, and files below /static/ are available without authentication. Photographs below /agent/photos/ instead require a signed, expiring URL: the URL is itself a capability and must be treated as confidential.

3.2 Turns, sessions, and dialogs

GroupPathsContract
Direct turnPOST /agent/turnJSON or an SSE stream on the same connection.
Resumable turnPOST /agent/turn/submit; GET /agent/turns/{id}/stream; GET /agent/turns/{id}Immediate acceptance, reconnectable events, and polling fallback.
History and judgementGET /agent/turns/recent; POST /agent/turns/{id}/feedback; POST /agent/turns/{id}/retryConversation history, explicit feedback, and retry.
Writer session/agent/session/register, takeover, ping, revoke, eventsOne writing device per user-channel pair and explicit transfer.
Dialogs/agent/dialog/{id}/form, submit, cancel, preview, contextInput collection with ownership and, when needed, a short-lived delegable capability.
Attachments/agent/gallery/{turn_id}, /agent/photos/{turn_id}/{idx}, /agent/photos/webOwner-bound gallery; signed local-file URLs; web proxy with SSRF defences and size limits.

3.3 Administration

Every route below /admin, except the login entry, requires the administrator role. The main areas are:

The code-level route registries remain authoritative. This grouping explains the surface without freezing a route count that would become stale.

4. Identity and authorisation

Middleware assigns one of three roles: anonymous, user, or admin:

  1. a Bearer equal to the administrator key grants admin;
  2. another Bearer is valid only if it matches a registered device;
  3. without a Bearer, a valid administrator or user cookie grants the corresponding role; the user cookie is also checked against the pairing registry, which is the revocation authority;
  4. when no credential was presented, selected local addresses may receive user as an installation-compatibility path;
  5. otherwise the caller remains anonymous.

An invalid Bearer never falls through to local-network trust. CF-Connecting-IP and X-Forwarded-For are considered only when the TCP peer belongs to METNOS_TRUSTED_PROXIES; an ordinary client cannot declare itself local through a header.

The administrator key lives at PATH_USER_CONFIG/admin.key, is created with mode 0600, and also derives signatures and encryption keys through separate domains. The administrator cookie lasts seven days and is HttpOnly, Secure, and SameSite=Strict; the user cookie lasts ninety days and is HttpOnly, Secure, and SameSite=Lax.

5. Sessions and per-user isolation

The active session is keyed by (user_id, channel). Hosts and guests can therefore use the chat concurrently without sharing a lease, conversation, or browser-local memory. The client also separates tokens, command buffers, and history through an opaque scope derived from the user.

When a second device finds an active session, the server issues a one-time token and leaves three choices to the interface:

Transfer rechecks both user and old writer atomically. Recent history requires a conversation belonging to the user. Turn status, stream, gallery, feedback, and retry also verify the turn owner: knowledge of a turn_id does not grant access. Older records without owner_user_id use only deterministic conversation or actor bindings; an identity-store failure is denied rather than converted into permission.

6. Direct and resumable turns

6.1 Direct request

POST /agent/turn accepts JSON or multipart data containing a request and reference images. With Accept: text/event-stream it sends thinking, progress, and tool_call events followed by final or error; periodic comments keep the connection alive. Without SSE it returns one JSON document.

6.2 Execution independent of the connection

POST /agent/turn/submit first reserves pool capacity and then returns 202 with a turn_id and stream_url. The turn continues if the page refreshes or the network drops. The stream accepts Last-Event-ID and replays later events; GET /agent/turns/{id} provides a polling fallback.

In-memory events remain for a bounded interval after completion. Once they are gone, the status route searches the persisted daily record. Persistence does not replace the ownership check.

6.3 Bounded capacity

Blocking turns pass through a dedicated pool with a global limit, a finite queue, and a per-principal limit. If capacity cannot be reserved within the admission window, the server returns 503 turn_capacity_exhausted with Retry-After: 1. A disconnect does not release capacity prematurely while the worker thread is still running.

7. HTML, JSON, and caching

Administration collections that use negotiate_collection return HTML when Accept contains text/html, and JSON otherwise. The body receives a SHA-256-derived ETag; an equal If-None-Match produces 304. This does not apply to every route: some are HTML-only, JSON-only, or SSE.

The chat uses no-cache, no-store so the client receives the current contract. Negotiated administration responses have a short private cache; model configuration and services use no-store. Signed attachment URLs have their own lifetime and must not be confused with a user session.

8. Settings and localisation

Settings uses Jinja2 templates, the runtime/ui_surfaces.py registry for navigation and descriptions, htmx for selected updates, and uPlot only in the executor-statistics view. There is no front-end build step.

Every visible string must come from the i18n catalog or already-localised data. Language is an individual user preference held in a request-local ContextVar; a new language requires catalog entries, a detection lexicon, and corresponding documentation rather than phrase-specific conditions in templates.

Settings is the interactive administration interface. The HTML report generated by runtime.observability is a separate artifact described in operational observability.

9. Network and capacity boundaries

10. Verification and canonical sources

curl -fsS http://127.0.0.1:8770/agent/health
curl -fsS http://127.0.0.1:8770/.well-known/metnos.json
curl -fsS -H "Authorization: Bearer <administrator-key>" \
  http://127.0.0.1:8770/agent/stack/health

The first probe proves liveness only. The third is suitable evidence before a restart or a full-stack test. Suites below tests/runtime/http/ cover authentication, sessions, turn isolation, SSE, caching, uploads, rendering, and administration pages; this guide does not freeze a test count.