Skip to main content

Overview

Publishing exposes one conductor profile over a public HTTPS surface served by the separate chat-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/runs works)
  • 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
Generate both with:

Step 1 — Configure Conductor for the Internal Listener

The conductor now serves two listeners: the public dashboard port and a private INTERNAL_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 a SUPERUSER 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.
Point 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.
The response includes the new 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.
Persist the token immediately. Re-fetching the keys list returns metadata only.

Step 5 — Call the Public Endpoint

Sync Chat

Streaming Chat

Continuing a Conversation

Thread the conversation_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.
Full unpublish is 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

The gateway returns 401 unauthorized on the very next request that uses the revoked bearer.

Rotate the Bearer Without Downtime

  1. Issue a new key
  2. Roll the new token out to every client
  3. Revoke the old key
The two keys are accepted in parallel; revoke only after every client carries the new bearer.

Rotating the Signing Key

The gateway-to-conductor HMAC is a shared secret. Rotate by overlapping the previous and current keys on both sides.
  1. Generate a new base64 key
  2. Set CHAT_GATEWAY_SIGNING_KEY_PREVIOUS to the current key on conductor and gateway; set CHAT_GATEWAY_SIGNING_KEY_CURRENT to the new value
  3. Roll restart both services
  4. After at least one nonce window (60 s), remove CHAT_GATEWAY_SIGNING_KEY_PREVIOUS and roll restart again
Mismatched keys produce 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:
  1. Issue new keys to every client first
  2. Update the pepper on conductor and gateway
  3. Revoke the old keys
There is no overlap window for the pepper. A future enhancement may add AGENT_API_KEY_PEPPER_PREVIOUS.

Observability

The chat gateway emits structured JSON logs with service=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:

Common Smoke Test

After deploy, this five-command sequence proves the surface end-to-end:
See Chat Gateway API for full request and response schemas, and Conductor publish endpoints for every conductor route this guide touches.