> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orcapods.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Chat Gateway API

> Public HTTP API for tenant-published agents.

## Base URL

Use the externally advertised chat-gateway host. Published agent rows include `publicUrl` when `CHAT_GATEWAY_PUBLIC_HOST` is set on the conductor:

```bash theme={null}
https://agents.example.com
```

All routes require:

```bash theme={null}
Authorization: Bearer ao_<env>_<token>
```

The bearer is minted from the conductor publish API for one published profile. It is scoped to the URL tenant and agent slug, checked against allowed origins when `Origin` is present, and rate-limited per key.

***

## Send a Chat Message

<ParamField path="POST /v1/chat/{tenant}/{agent}" />

Runs one message against a published agent. The gateway creates a conversation when `conversation_id` is omitted, otherwise it reuses the existing conversation's runtime session.

| Field             | Type   | Required | Description                                                 |
| ----------------- | ------ | -------- | ----------------------------------------------------------- |
| `message`         | string | Yes      | User message to send to the published agent                 |
| `conversation_id` | string | No       | Existing `conv_...` conversation to continue                |
| `metadata`        | object | No       | Free-form metadata; `end_user_id` is persisted when present |

```bash theme={null}
curl -X POST https://agents.example.com/v1/chat/acme/support-bot \
  -H 'Authorization: Bearer ao_prod_...' \
  -H 'Content-Type: application/json' \
  -d '{"message":"Summarize my latest ticket."}'
```

Successful synchronous responses include the durable conversation and public run ids:

```json theme={null}
{
  "conversation_id": "conv_abcd1234",
  "message": "Here is the summary...",
  "public_run_id": "prun_abcd1234"
}
```

If the run exceeds the published agent's `syncMaxDurationSeconds`, the endpoint returns `504` with `sync_timeout`, `conversation_id`, and `public_run_id`; use the run or stream endpoints below to resume.

***

## Stream a Chat Message

<ParamField path="POST /v1/chat/{tenant}/{agent}/stream" />

Starts a run and streams public Server-Sent Events. The request body is the same as [Send a Chat Message](#send-a-chat-message).

```bash theme={null}
curl -N -X POST https://agents.example.com/v1/chat/acme/support-bot/stream \
  -H 'Authorization: Bearer ao_prod_...' \
  -H 'Content-Type: application/json' \
  -d '{"message":"Draft a customer reply."}'
```

Events:

| Event   | Payload                                               | Notes                                                    |
| ------- | ----------------------------------------------------- | -------------------------------------------------------- |
| `delta` | `{ "text": "..." }`                                   | Assistant text chunk                                     |
| `tool`  | `{ "id": "...", "name": "...", "status": "running" }` | Only when the published agent enables `exposeToolEvents` |
| `tool`  | `{ "id": "...", "status": "ok or error" }`            | Only when `exposeToolEvents` is enabled                  |
| `done`  | `{ "public_run_id", "conversation_id", "message" }`   | Terminal success                                         |
| `error` | `{ "code": "upstream", "message", "public_run_id" }`  | Terminal failure                                         |

The gateway also sends `: ping` heartbeat comments about every 15 seconds.

***

## Get a Public Run

<ParamField path="GET /v1/chat/{tenant}/{agent}/runs/{publicRunId}" />

Returns the terminal output for a public run, or `202 Accepted` while it is still `dispatching` or `running`.

```json theme={null}
{
  "public_run_id": "prun_abcd1234",
  "conversation_id": "conv_abcd1234",
  "message": "Final answer",
  "status": "ok"
}
```

`error` runs return `502`; lost runs return `410`.

### Resume a Public Run Stream

<ParamField path="GET /v1/chat/{tenant}/{agent}/runs/{publicRunId}/stream" />

Subscribes to an in-flight public run when its watcher is still alive. If the run is already terminal, the gateway emits one reconstructed `done` or `error` event from stored conversation history.

***

## Get a Conversation

<ParamField path="GET /v1/chat/{tenant}/{agent}/conversations/{id}" />

Returns recent conversation messages for the published agent. Query params:

| Query            | Description                          |
| ---------------- | ------------------------------------ |
| `limit`          | Max messages to return; default `50` |
| `include=errors` | Include stored error messages        |

```json theme={null}
{
  "conversation_id": "conv_abcd1234",
  "messages": [
    { "role": "user", "content": "Hello", "at": "2026-05-15T10:00:00Z" },
    { "role": "assistant", "content": "Hi", "public_run_id": "prun_abcd1234", "at": "2026-05-15T10:00:02Z" }
  ]
}
```

***

## Operations

The gateway requires the same Postgres cluster as the conductor, Redis, the published-agent API key pepper, and a private conductor URL:

| Variable                                | Description                                                                       |
| --------------------------------------- | --------------------------------------------------------------------------------- |
| `PORT`                                  | Public listen port; defaults to `8090`                                            |
| `CONDUCTOR_INTERNAL_URL`                | Private conductor internal listener URL; `*.railway.app` public URLs are rejected |
| `POSTGRES_URL` / `POSTGRES_DSN` / `PG*` | Shared Postgres DSN using a non-superuser `NOBYPASSRLS` application role          |
| `REDIS_URL`                             | Nonce replay and rate-limit backing store                                         |
| `CHAT_GATEWAY_SIGNING_KEY_CURRENT`      | Base64 HMAC key used to sign internal conductor requests                          |
| `CHAT_GATEWAY_SIGNING_KEY_PREVIOUS`     | Optional previous signing key during rotation                                     |
| `AGENT_API_KEY_PEPPER`                  | Base64 HMAC pepper used to hash public API keys                                   |
| `CHAT_GATEWAY_PUBLIC_HOST`              | Host used by conductor publish responses to derive `publicUrl`                    |
| `GATEWAY_RATE_LIMIT_BACKEND`            | `redis` or `memory`; defaults to `redis`                                          |
| `GATEWAY_SHUTDOWN_GRACE`                | Graceful shutdown timeout; defaults to `25s`                                      |

The gateway refuses to start when the Postgres role is a `SUPERUSER` or has `BYPASSRLS`, because `published_agents` and `gateway_runs` reads are tenant-scoped by RLS. Use the same application-role pattern as the conductor.

Health endpoints:

| Method | Path       | Description                               |
| ------ | ---------- | ----------------------------------------- |
| `GET`  | `/healthz` | Process liveness                          |
| `GET`  | `/readyz`  | Readiness for serving public chat traffic |
