Overview
Publishing exposes one conductor profile over a public HTTPS surface served by the separatechat-gateway binary. This guide walks operators through the full lifecycle: configuration, publish, key issuance, calling the surface, rotation, and revoke. See Publishing for the conceptual model.
Prerequisites
- A running conductor with Postgres and Redis wired up
- A non-superuser Postgres application role with
NOBYPASSRLS - A profile that already runs cleanly under conductor (
POST /api/runsworks) - DNS for the public chat host pointed at the chat-gateway service (for example
agents.example.com) - Two base64 secrets minted ahead of time:
- Signing key — shared HMAC secret between gateway and conductor
- API key pepper — HMAC pepper used to hash minted bearers
Step 1 — Configure Conductor for the Internal Listener
The conductor now serves two listeners: the public dashboard port and a privateINTERNAL_PORT that only the chat gateway should reach.
The conductor refuses to start when
INTERNAL_PORT is set but CHAT_GATEWAY_SIGNING_KEY_CURRENT is missing — that combination would silently accept unsigned internal traffic.
Postgres Role Requirement
When Postgres is configured, the conductor refuses to boot if the connecting role is aSUPERUSER or has BYPASSRLS. Tenanted tables rely on row-level security policies scoped by the per-transaction app.tenant_id setting, so a privileged role would silently read rows across tenants. Railway’s default postgres user is a superuser; create and use an application role instead.
POSTGRES_DSN or the POSTGRES_USER / POSTGRES_PASSWORD component variables at that role. The CREATE privilege is required because the conductor applies embedded schema migrations on boot.
Step 2 — Deploy the Chat Gateway
The gateway is a standalone binary (cmd/chat-gateway). The reference Dockerfile is at docker/Dockerfile.chat-gateway; the Railway service config is at docker/railway-chat-gateway.toml.
The gateway also refuses a Postgres role that is a
SUPERUSER or has BYPASSRLS, for the same tenant-isolation reason as the conductor. The boot log line chat-gateway.ready confirms conductor_internal_url, rate_limit_backend, and whether a previous signing key is present.
Step 3 — Publish a Profile
Publishing is a conductor API call against the public port. The dashboard “Publish” surface wraps the same endpoint; the curl examples below match it byte-for-byte.pub_... row plus the publicUrl derived from CHAT_GATEWAY_PUBLIC_HOST:
Slugs are immutable. To change a slug, unpublish and republish.
Step 4 — Mint an API Key
API keys are HMAC-hashed with the pepper; the plaintext token is returned exactly once and never recoverable. See API Keys for the auth model.token immediately. Re-fetching the keys list returns metadata only.
Step 5 — Call the Public Endpoint
Sync Chat
Streaming Chat
Continuing a Conversation
Thread theconversation_id from any prior response back in the body. The gateway reuses the same runtime session under the hood.
Resume an In-Flight Run
runs/{id} returns 202 Accepted while the run is still dispatching or running, then the terminal payload once durable.
Step 6 — Update or Disable a Published Agent
PATCH /api/profiles/{name}/published accepts any subset of the published knobs plus enabled. Operators can toggle tool-event streaming on an already-published agent with exposeToolEvents. Setting enabled: false is a fast kill switch that survives without losing history.
DELETE /api/profiles/{name}/published. The row is soft-deleted with unpublished_at so the slug becomes immediately reusable for a different profile.
Step 7 — Revoke or Rotate Keys
Revoke One Key
401 unauthorized on the very next request that uses the revoked bearer.
Rotate the Bearer Without Downtime
- Issue a new key
- Roll the new token out to every client
- Revoke the old key
Rotating the Signing Key
The gateway-to-conductor HMAC is a shared secret. Rotate by overlapping the previous and current keys on both sides.- Generate a new base64 key
- Set
CHAT_GATEWAY_SIGNING_KEY_PREVIOUSto the current key on conductor and gateway; setCHAT_GATEWAY_SIGNING_KEY_CURRENTto the new value - Roll restart both services
- After at least one nonce window (60 s), remove
CHAT_GATEWAY_SIGNING_KEY_PREVIOUSand roll restart again
502 upstream at the gateway and signature_invalid logs on the conductor’s internal listener.
Rotating the API Key Pepper
The pepper is the HMAC key over every minted bearer hash. Rotating it invalidates every existing key — treat it as a security incident:- Issue new keys to every client first
- Update the pepper on conductor and gateway
- Revoke the old keys
AGENT_API_KEY_PEPPER_PREVIOUS.
Observability
The chat gateway emits structured JSON logs withservice=chat-gateway. Key events:
Accepted public chat requests are also flushed into
usage_records as the ingress meter. Operators can read the tenant-wide total and top published agents from GET /api/usage, or the scoped agent-detail series from GET /api/profiles/{name}/metrics.
Prometheus metrics are exposed on /metrics: