Gateway & Live Config
The gateway is RantAIClaw’s HTTP control plane. It runs alongside the agent — typically inside the daemon process — on a configurable port (default 3000, default bind 127.0.0.1).
This page describes what’s actually wired into the running router today and what the auth model looks like.
What the gateway is for
- Pairing and bearer auth — issue tokens to clients (the agent-runner, scripts, external orchestrators)
- Webhook ingress — receive HTTP webhooks as agent inputs
- Channel-specific webhook endpoints — WhatsApp Cloud, Linq, Nextcloud Talk
- Trigger ingress —
/triggers/{*path}routes inbound HTTP into skills/messages defined inwebhook-routes.json /api/v1/*surface — sessions, skills, memory, channels, providers/models, status, doctor- Live config mutation —
GET/PUT /api/v1/config/*, secret presence, MCP servers, Telegram connect, Knowledge Base keys, approval resolution - Knowledge Base API —
/api/v1/kb/*(documents, search, graph, groups) — see Knowledge Base - Health and metrics —
/health,/metrics(Prometheus text)
Live-config endpoints in src/gateway/config_api.rs are mounted — config_api::router() is merged onto the app in src/gateway/mod.rs, and so is the Knowledge Base router (crate::kb::axi::api::router(), when built with the default kb feature). See The mounted live-config surface below.
Bind safety
Defaults to 127.0.0.1 (loopback only). To bind a non-loopback host, you must opt in:
[gateway]
host = "0.0.0.0"
port = 3000
allow_public_bind = trueWithout allow_public_bind = true, the gateway refuses to start with a non-loopback bind. The check is in src/gateway/mod.rs:
🛑 Refusing to bind to 0.0.0.0 — set [gateway].allow_public_bind = trueTunnel exception: if [tunnel].provider != "none" (e.g. cloudflared, ngrok), the public-bind guard is bypassed because the tunnel is expected to terminate TLS and gate access. Document this clearly in your deployment if you rely on it.
The env var RANTAICLAW_ALLOW_PUBLIC_BIND=1 works as an alternative to the config flag.
Pairing and bearer tokens
There is no rantaiclaw gateway pair CLI subcommand. The gateway command is a leaf with --port and --host only.
Pairing is HTTP-driven:
- Start the gateway. A 6-digit pairing code is printed to stdout (CSPRNG-generated, rejection-sampled to avoid modulo bias).
- Client submits the code via
POST /pairwith headerX-Pairing-Code: <code>. - Server returns a 256-bit token (64 hex chars) once.
- Server stores only the SHA-256 hash of the token in
[gateway].paired_tokensinconfig.toml. The plaintext is never persisted.
PAIR_CODE=123456
curl -X POST http://localhost:3000/pair \
-H "X-Pairing-Code: $PAIR_CODE"
# → { "token": "abcd...", "issued_at": "..." }Subsequent requests use Authorization: Bearer <token>. Auth is flat — there are no scopes. A valid token has full access to authenticated routes.
Tokens cannot be recovered if lost; pair again to issue a new one.
Real HTTP endpoint surface
Routes mounted in src/gateway/mod.rs plus the api_v1 router merged on top:
| Method | Path | Auth |
|---|---|---|
| GET | /health | none |
| GET | /metrics | none (Prometheus text) |
| POST | /pair | one-time X-Pairing-Code header |
| POST | /webhook | bearer + optional X-Webhook-Secret |
| GET / POST | /whatsapp | Meta verify-token (GET) / HMAC X-Hub-Signature-256 (POST, when app_secret set) |
| POST | /linq | HMAC signing-secret (when set) |
| POST | /nextcloud-talk | HMAC (when webhook_secret set) |
| POST | /triggers/{*path} | bearer |
| GET / POST | /tasks | bearer |
| GET / PUT / DELETE | /tasks/{id} | bearer |
| POST | /tasks/{id}/review | bearer |
| GET / POST | /tasks/{id}/comments | bearer |
| GET | /tasks/{id}/events | bearer |
| GET | /api/v1/version | none |
| GET | /api/v1/status | bearer |
| GET | /api/v1/doctor | bearer |
| POST | /api/v1/agent/chat | bearer |
| GET | /api/v1/sessions | bearer |
| POST | /api/v1/sessions/search | bearer |
| GET | /api/v1/sessions/{id} | bearer |
| PUT | /api/v1/sessions/{id}/title | bearer |
| GET | /api/v1/insights | bearer |
| GET | /api/v1/skills | bearer |
| GET | /api/v1/skills/{name} | bearer |
| GET | /api/v1/memory | bearer |
| GET | /api/v1/memory/stats | bearer |
| GET / PUT | /api/v1/personality | bearer |
| GET | /api/v1/channels | bearer |
| GET | /api/v1/providers | none |
| GET | /api/v1/providers/{id}/models | bearer |
| POST | /api/v1/providers/{id}/models/refresh | bearer (fetches live model list) |
| POST | /api/v1/approvals/{id} | bearer (resolve a pending approval) |
| GET | /api/v1/config | bearer |
| PUT | /api/v1/config/model | bearer |
| PUT | /api/v1/config/autonomy | bearer |
| GET / PUT | /api/v1/secrets | bearer (presence-only; keys never returned) |
| POST / DELETE | /api/v1/config/mcp_servers/{name} | bearer |
| POST / DELETE | /api/v1/channels/telegram | bearer |
| GET / PUT | /api/v1/config/knowledge | bearer (kb feature; presence-only) |
Knowledge Base router (src/kb/axi/api.rs, merged when built with the default kb feature). These routes bypass the gateway-wide 64 KiB body cap and 120 s timeout — the subtree sets its own larger upload limit (KB_UPLOAD_MAX_BYTES = 32 MiB) and longer ingest timeout (600 s) so document uploads and per-chunk embeds aren’t rejected or cut off:
| Method | Path | Auth |
|---|---|---|
| POST | /api/v1/kb/search | bearer |
| POST / GET | /api/v1/kb/documents | bearer (POST ingests; GET lists) |
| GET / DELETE | /api/v1/kb/documents/{id} | bearer |
| GET | /api/v1/kb/documents/{id}/intelligence | bearer |
| POST | /api/v1/kb/documents/{id}/re-extract | bearer |
| GET / POST | /api/v1/kb/groups | bearer |
| GET / PUT / DELETE | /api/v1/kb/groups/{id} | bearer |
| GET / POST | /api/v1/kb/groups/{id}/documents | bearer |
| DELETE | /api/v1/kb/groups/{id}/documents/{doc_id} | bearer |
| GET | /api/v1/kb/graph | bearer |
| GET | /api/v1/kb/drift | bearer |
| POST | /api/v1/kb/re-embed | bearer |
See Knowledge Base for what these endpoints do. There is no /mcp/servers route (MCP servers are managed via POST/DELETE /api/v1/config/mcp_servers/{name}), and no rantaiclaw gateway subcommand for pairing.
Webhook authentication — three flavors
Different endpoints use different verification strategies:
/webhook (generic prompt entry)
- Bearer token required (when
[gateway].require_pairing = true) - Optional
X-Webhook-Secretheader — server SHA-256-hashes the value and compares against the configured hashed secret withconstant_time_eq - This is a shared-secret check, not an HMAC over the request body
Channel-specific endpoints (HMAC over body)
POST /whatsapp—X-Hub-Signature-256: sha256=<hex>HMAC-SHA256 over the raw body, verified withmac.verify_slice(constant-time)POST /linq— channel-specific signing secret, HMAC-SHA256POST /nextcloud-talk—X-Nextcloud-Talk-Random+X-Nextcloud-Talk-Signatureheaders, HMAC-SHA256
These header names are hard-coded per channel, not user-configurable. Missing or invalid signatures return 401 with a JSON body {"error": "..."}.
Trigger routes — /triggers/{*path}
The trigger route is fed by <config_dir>/webhook-routes.json (typically written by an agent-runner or external orchestrator), not by config TOML. Schema:
[
{ "path": "/deploys", "skill": "deploy-checker", "message": "{repo} deployed: status={status}" },
{ "path": "/incidents", "skill": "incident-triage", "message": "Alert: {title}" }
]There is no HMAC, no template engine — just literal placeholders that get formatted from query params. Auth is bearer-only.
Rate limits and idempotency
Per-client-IP sliding-window rate limits:
/pair— 10/min/webhook,/triggers/*— 60/min
Idempotency: X-Idempotency-Key header on /webhook deduplicates retries within the configured TTL (default 300s).
The mounted live-config surface
src/gateway/config_api.rs is wired into the running router — config_api::router() is merged in src/gateway/mod.rs alongside api_v1::router(). The mounted, bearer-authenticated handlers are:
GET /api/v1/config— read the effective config (secrets redacted)PUT /api/v1/config/model— hot-swap the active modelPUT /api/v1/config/autonomy— set the autonomy levelGET/PUT /api/v1/secrets— read/write secret presence only; values are never returnedPOST/DELETE /api/v1/config/mcp_servers/{name}— add or remove an MCP server entryPOST/DELETE /api/v1/channels/telegram— connect or disconnect a Telegram channel from the consoleGET/PUT /api/v1/config/knowledge— read presence / set the Knowledge Base embedding + OCR keys (only when built with thekbfeature)
Note the shapes are GET/PUT/POST/DELETE on concrete paths — not an old PATCH /config/* design. Writes call Config::save(), so they persist to config.toml and survive a restart. The Knowledge Base router (crate::kb::axi::api::router()) is merged the same way and adds the /api/v1/kb/* surface — see Knowledge Base.
What the daemon does with the gateway
When you run rantaiclaw daemon, the supervisor starts:
- the gateway HTTP server on the configured port
- each enabled channel’s listen task
- the heartbeat / scheduler / cron loops
Each component is supervised with exponential backoff restart on crash, so a misbehaving channel won’t take down the gateway and vice versa.
Reading the code
src/gateway/mod.rs— server bootstrap, route registration, handlers for/health,/metrics,/pair,/webhook, channel webhooks, triggerssrc/gateway/api_v1.rs— the/api/v1/*router and bearer-auth middlewaresrc/gateway/task_handlers.rs—/tasks/*handlerssrc/gateway/config_api.rs— mounted live-config surface (config_api::router(), merged inmod.rs)src/kb/axi/api.rs— the/api/v1/kb/*Knowledge Base router (merged when thekbfeature is built; larger body/timeout caps)src/security/pairing.rs— pair flow, token generation, hash storage, constant-time comparesrc/security/pairing_store.rs— on-demandchannels paircodes (SHA-256-hashed,0600, TTL + max-uses)