# Gateway API Reference

The GoClaw gateway exposes the Web UI, web chat over Server-Sent Events,
WebSocket chat, REST administration APIs, webhook ingress, media endpoints, and
an OpenAI-compatible chat API.

Routes are registered in `cmd/gateway/startup.go` (`registerAPIRoutes`, which
also mounts the trace routes of `internal/gateway/handler_traces.go`) and in
`cmd/gateway/main.go` (the Web UI's files, `/canvas/` and `/uploads/`), and
implemented under `internal/gateway`.

## Authentication

Every route on this page needs the gateway token, `gateway.auth.token`. The
gateway does not start without one. These are the exceptions:

| Route | How it is checked |
|---|---|
| `/health`, `/healthz`, `/ready`, `/readyz` | Not checked. |
| The Web UI's own files (`/`, its scripts, styles and images) | Not checked; every API call the page makes sends the token. |
| `/goclaw/hook/…` and the channel webhooks (`/hook/…`) | Each checks its sender's own credential. See [Webhook receiver](#webhook-receiver). |
| `/goclaw/view/<slug>` | The page's own view token (`?token=<view_token>` or an `X-View-Token` header), or the gateway token. |
| `/api/app/*`, `/ws/app` | The App API's own credential. See [App API](#app-api). |

Send the token in any of these:

- an `X-Goclaw-Token: <token>` header;
- an `Authorization: Bearer <token>` header;
- a `?token=<token>` query parameter, for clients that cannot set a header
  (`EventSource`, a browser WebSocket);
- an `X-Goclaw-Token` cookie. The gateway sets it when a browser opens the
  one-time login link it logs at startup (`Web UI: http://…/?auth_code=…`): the
  code is valid for 5 minutes, and for 30 seconds after its first use. The
  cookie lasts 7 days, and the gateway redirects to the same path without the
  code. The code is not the token. The cookie is taken only from the dashboard
  itself: a request to `/api/`, `/v1/` or `/plugins/` that carries it alone and
  that the browser marks as started by another site (`Sec-Fetch-Site:
  same-site` or `cross-site` — a page on another port of the same host is
  same-site) is answered `403`. Send the token in a header there instead.

A missing or wrong token is answered `401 {"error":"unauthorized"}`. When
`gateway.auth.token` is a `${secret:…}` reference the vault cannot resolve, every
request is answered `500`.

Requests that carry the token are not counted against `gateway.rate_limit`;
neither are health probes and the Web UI's files. When webhooks share the main
port (no `gateway.ingress.port`), `gateway.ingress.allow_ips` / `allow_preset`
apply to every route on it; loopback is always let through.

## Web Chat

### `POST /api/chat`

The Web UI's chat. It runs one message on the default agent as the admin (role
`system`) and streams the reply as Server-Sent Events.

```json
{
  "session_id": "admin-session",
  "content": "Hello",
  "attachments": []
}
```

| Field | Notes |
|---|---|
| `session_id` | Optional; defaults to `admin-session`. |
| `content` | The message (`message` is accepted as an alias). Required unless `attachments` has an item. |
| `attachments` | Files uploaded first with `/api/upload-workspace` or `/api/media/upload`, each as `{media_id, media_url, local_path, filename, mime_type, size}`. |

A message sent while an earlier one in the same session is still being answered
waits for it. If the request ends while it waits, the answer is `409`.

The response is `text/event-stream`. Each frame is `event: <name>` with a JSON
`data:` line:

| Event | Data |
|---|---|
| `token` | `content` (also `delta`): the next piece of the reply. |
| `tool_start` | `tool`: the tool the agent called. |
| `tool_result` | `tool`: the tool that finished. |
| `canvas` | `title`, `content` (HTML): a canvas artifact. |
| `buffer_reset` | The reply is being retried: discard the text received so far. |
| `error` | `error`: what went wrong. No `done` follows. |
| `done` | `content` (the whole reply), `attachments` (files the agent produced, served under `/canvas/`), `prompt_tokens`, `output_tokens`, `elapsed_ms`, `session_id`. |

A reset trigger (`session.reset_triggers`, by default `/new` and `/reset`)
clears the session, and `@config:`, `@notes:`, `@sop:` directives and
`/subagents` commands are answered without the model; each ends with `done`, or `error` when it fails.
The directives need an administrator, which this endpoint always is. If
the client disconnects, the agent still finishes (for up to 10 minutes) and the
reply is saved to the session.

## WebSocket

### `GET /gateway`

Upgrades to a WebSocket connection, for chat clients other than the Web UI (the
Web UI uses [`POST /api/chat`](#post-apichat)). When `gateway.allowed_origins`
is set, only those origins may connect.

Message envelope:

```json
{
  "id": "msg-123",
  "kind": "inbound",
  "session_id": "admin-session",
  "payload": {
    "channel": "web",
    "peer_id": "web-tab",
    "content": "Hello"
  },
  "timestamp": "2026-06-20T10:00:00Z"
}
```

Supported message kinds:

| Kind | Direction | Purpose |
|---|---|---|
| `inbound` | client -> gateway | User message and optional attachments. |
| `outbound_chunk` | gateway -> client | Streaming token chunk. |
| `outbound` | gateway -> client | Final assistant response. |
| `event` | gateway -> client | Tool lifecycle and buffer-reset events. |
| `canvas` | gateway -> client | Rich canvas artifact update. |
| `error` | gateway -> client | User-facing processing error. |

Payloads:

| Kind | Payload |
|---|---|
| `inbound` | `channel`, `account_id`, `peer_id`, `content`, and optional `attachments`, each `{media_id, media_url, local_path, filename, mime_type, size}`. |
| `outbound_chunk` | `content`: the next piece of the reply. |
| `outbound` | `content`, and `attachments` the agent produced (served under `/canvas/`). |
| `event` | `{"event_type":"tool_start","tool_name","args"}`, `{"event_type":"tool_result","tool_name","result","error","elapsed_ms"}` or `{"event_type":"buffer_reset"}` (discard the text received so far). |
| `canvas` | `title`, `content` (HTML). |
| `error` | `error`, and `ref_id`: the `id` of the message it answers. |

Sessions: on channel `web`, a `session_id` that starts with `admin-` is used as
given; any other value, or none, gives the connection its own session,
`admin-conn-<16 hex digits>`. Web messages run as the admin (role `system`). On
any other channel the `session_id` is used as given, and without one the
session is `p2p:<peer_id>:<default agent>`.

A connection's messages are handled one at a time. Reset triggers, directives
and `/subagents` commands work as in [`POST /api/chat`](#post-apichat). Off the
`web` channel a message runs with the role GoClaw resolves for its `channel` and
`peer_id`, and `@config:`, `@notes:` and `@sop:` need a peer paired as `admin`:
anyone else is told so and nothing is changed (the message and the refusal stay
in the session, as for any directive).

## Core Admin APIs

### `GET /api/stats`

Returns gateway health, provider/model information, session count, connected
channels, runtime ingress status, and version metadata.

### `GET /api/gateway`

Returns detailed runtime status, ingress settings, rate-limit state, and recent
access logs. Supports `?limit=N`.

### `GET /api/doctor`

Runs health checks and returns diagnostic results. With `?stream=1` the results
arrive as Server-Sent Events, one `check` event per check as it finishes and a
final `done` event with `{"total", "ok"}`.

### `GET /api/inspect`

Returns a combined diagnostic view with sessions, memory snippets, database
path, state directory, and server time.

### `GET|POST /api/config`

Reads or mutates active configuration. `GET` returns the whole configuration
keyed as `config.yaml` is (`tools.http.allow`), with secrets masked;
`GET ?key=ai.provider` returns a single key. `POST` supports mutation actions
such as `set`, `append`, and `remove`.

### `GET|POST|DELETE /api/secrets`

Manages the encrypted secret vault. Secret values are masked when listed.

### `GET|POST /api/integrations`

Lists, tests, saves and deletes the external services the tools use: GitLab,
SonarQube, Grafana, Graylog, NetBox, Uptrace, Zabbix, Wazuh and datasources.
`GET` returns each definition with credentials masked (`${secret:…}` references
are shown as written). `POST` takes `{"action", "type", "name", "config"}`:

- `test` probes the definition as the default agent and succeeds only when the
  credentials are accepted. `warnings` names agents that override the enclosing
  block (and so will not see it) or whose HTTP policy blocks the host.
- `save` merges `config` onto the stored definition — a key left out keeps its
  stored value, and the mask keeps a stored secret only while the URL or host is
  unchanged. `create: true` refuses an existing name (`409`);
  `store_secrets: true` moves typed credentials into the vault as
  `integrations.<type>.<name>.<field>` and writes a `${secret:…}` reference.
- `delete` removes the definition; a datasource named by
  `org_directory.sql.datasource` is refused (`409`).

### `GET|POST /api/pairing`

Lists pending/approved channel pairings and approves, revokes, or updates user
roles. `GET` takes an optional `?channel=`. `POST` takes an `action`:
`{"action":"approve","code","role"}` approves a pairing code,
`{"action":"revoke","user_id"}` removes a user, and
`{"action":"update_role","user_id","role"}` changes a user's role. Approvals made
here take effect in the running gateway at once.

## Sessions

### `GET|POST|DELETE /api/sessions`

- `GET`: Lists persisted sessions, each as `{id, title, message_count, last_seen}`.
  The messages themselves are served by [`GET /api/session`](#get-apisession).
- `POST {"action":"create","id":"admin-name","name":"Label"}`: Creates an admin
  Web UI session. The `id` must start with `admin-`; without one it is
  `admin-<unix milliseconds>`. `name` is optional.
- `POST {"action":"rename","id":"admin-name","name":"New label"}`: Renames a
  session.
- `POST {"action":"clear"}`: Deletes all sessions; the response says how many.
- `DELETE ?id=<session-id>`: Deletes one session.

Session creation is persisted immediately, including empty sessions, so Web UI
refreshes do not make newly created sessions disappear.

Each of these changes waits until a message being answered in that session is
done, so the reply cannot write the old session back over the change. A request
that ends while it waits is answered `503`; `clear` then reports how many
sessions it had already deleted.

### `GET /api/session`

Returns paginated message history for one session.

Query parameters:

| Parameter | Required | Notes |
|---|---:|---|
| `id` | yes | Session ID. |
| `limit` | no | Defaults to `50`; a value that is not a number from 1 to `500` gives `50`. |
| `offset` | no | Offset from the newest message. |

The response includes `history`, parallel `meta`, `total`, `limit`, `offset`,
and `username`. Web chat attachment previews are stored in `meta[].attachments`.

## Memory and Search

### `GET /api/memory`

Returns memory records grouped for the Web UI.

### `GET|POST /api/memory/core`

Reads or updates an agent's core instruction: `?agent=<id>`, the default
agent's when left out. The ID is used as given: where the default agent has an
ID of its own and no agent is configured as `main`, `?agent=main` reads an
instruction stored under `main`, which no run follows.

### `GET|POST /api/memory/bootstrap-file`

Reads or updates controlled bootstrap files such as `AGENTS.md`, `TOOLS.md`, and
other admin-managed prompt files, in the workspace of `?agent=<id>` (the default
agent's when left out).

### Memory maintenance endpoints

| Endpoint | Purpose |
|---|---|
| `/api/memory/replay` | Replay archived/session memory into the memory engine. |
| `/api/memory/reindex` | Rebuild memory indexes. |
| `/api/memory/stats` | Return memory statistics. |
| `/api/memory/prune` | Prune old or low-value memory. |
| `/api/memory/dedupe` | Deduplicate memory records. |
| `/api/memory/forget` | Delete selected memory. |
| `/api/memory/clear` | Clear memory data. |
| `/api/memory/relearn` | Trigger relearning workflows. |
| `/api/memory/revise` | `POST {"dry_run"}`: weigh each learned fact or preference against older ones on the same subject and retire those it replaces (tagged `superseded`, not deleted). `goclaw memory revise` calls it. |
| `/api/memory/revisions` | `GET ?run=&record=&limit=`: the recorded decisions that one learned record replaces another, newest first (`goclaw memory revisions`). |
| `/api/memory/revisions/undo` | `POST {"record":"<memory-id>"}` or `{"run":"<run-id>"}`: restore what a decision retired; an undone decision is not made again (`goclaw memory revisions undo`). |
| `/api/memory/backfill-metadata` | Populate missing memory metadata. |
| `/api/memory/notes` | Read or update agent notes. |
| `/api/memory/export` | Export memory. |
| `/api/memory/import` | Import memory. |
| `/api/memory/graph` | Return knowledge graph data. |

### `GET /api/search`

Searches memory and archived material. Common parameters include `q` and
`source`.

## Agents, Tools, and RAG

| Endpoint | Purpose |
|---|---|
| `GET /api/agents` | List configured agents and bindings. Each agent carries `tool_policy`: its effective tool, shell, HTTP and git policy and the keys it overrides. |
| `GET\|POST\|DELETE /api/skills` | `GET` lists installed and built-in skills. `POST` takes an `action`: `install`, `toggle` (turn a skill on or off), `verify` or `save`. `DELETE ?name=<skill>` removes a user-installed skill. |
| `GET\|POST /api/skills/quality` | A skill's quality score: `GET ?name=<skill>` returns the cached score or evaluates it; `POST {"name","force":true}` evaluates it again. |
| `GET /api/tools` | List registered tools. |
| `POST /api/rag-test` | `{"query"}`: show which tools and skills Tool RAG and Skill RAG would select for it, with their scores. |
| `GET\|POST /api/mcp` | `GET` lists the configured MCP servers. `POST` takes an `action`: `test` connects a draft definition and lists its tools, `call` runs one tool, `save` writes the definition into `config.yaml`, `delete` removes it. |

## Automation

| Endpoint | Purpose |
|---|---|
| `/api/cron` | `GET` lists cron jobs with their next run. `POST {"action","id"}` with `run`, `enable`, `disable`, `update` (with `"schedule"`) or `delete`; a system job (`sys-…`) cannot be deleted, only disabled. Jobs are created with the agent's `cron` tool; this API has no create. |
| `/api/sop` | Manage SOP definitions. `GET` lists them, or with `?id=` returns one with `cron_schedule`, its cron job's schedule. `POST` saves one: on an existing SOP, a field the body leaves out (top level, or in a step with the same `id`) keeps its stored value, and a changed definition archives the previous version. A `cron_schedule` in the body creates or reschedules the SOP's cron job; one that cannot be applied is returned as `warning`. `DELETE ?id=` removes one. |
| `/api/sop/executions` | List SOP executions. |
| `/api/sop/run` | `POST {"id"}`: start a run of an SOP in the background; the answer has its `execution_id`. A paused SOP, or one overdue for review, is answered `409` with the reason. |
| `/api/sop/approve` | `POST {"job_id","action":"approve"\|"reject","reason"}`: approve or reject an SOP step that is waiting for approval. A step nobody decides fails after 1 hour. |
| `/api/sop/validate` | Validate an SOP definition. |
| `/api/sop/channels` | Pin SOP notification channels. |
| `/api/capability-requests` | `GET` lists capability requests; `?status=` filters them. |
| `/api/capability-requests/resolve` | `POST {"id","status","resolved_by","note"}`, with `status` `resolved` or `rejected`. |

## Webhooks and Plugins

| Endpoint | Purpose |
|---|---|
| `POST /api/webhook` | Generic inbound message endpoint. See [`POST /api/webhook`](#post-apiwebhook). |
| `GET /api/hooks` | Registered gateway hook events (built-in and Go extensions) with their handlers. The Hooks page's rules are `/api/hooks/rules` (see [Other Routes](#other-routes)). |
| `GET\|POST /api/webhooks` | List webhook routes (`GET`); create, update, enable, disable or delete a stored route (`POST` with `action`). See [Webhook routes API](#webhook-routes-api). |
| `GET\|DELETE /api/plugins` | `GET` lists the installed agent plugins (the Plugins page); `DELETE ?name=<plugin>` turns one off for every agent and removes it. |
| `GET /api/extensions` | Discovered Go/OpenClaw extensions (`extensions/` directories) and which are active. |
| `GET /api/audit` | Query webhook audit logs: `?channel=`, `?event=`, `?user=`, `?limit=` (default `100`, at most `500`). With `?kind=admin`, the administrative trail (SOP deletes, channel pins) instead: `?action=`, `?actor=`, `?resource=`, `?limit=`. |
| `POST /goclaw/hook/<type>[/<key>]` | The webhook receiver outside systems (GitLab, GitHub, Graylog, Zabbix, …) deliver events to. See [Webhook receiver](#webhook-receiver). |

When the Teams or Facebook channel, or the Telegram channel in webhook mode, is
enabled, its webhook is mounted on the webhook listener at `/hook/msteams`,
`/hook/facebook` or `/hook/telegram`, or at `/hook/<webhook_secret_slug>/<channel>`
when `gateway.webhook_secret_slug` is set.

### `POST /api/webhook`

Runs one message on the default agent and answers with the whole reply. It needs
the gateway token on every port, a separate `gateway.ingress.port` included:

```json
{"channel": "discord", "account_id": "discord", "peer_id": "u_123456", "content": "Hello"}
```

```json
{"content": "Hi! How can I help?"}
```

The session is `p2p:<peer_id>:<default agent>`, and the message runs with the
role GoClaw resolves for that `channel` and `peer_id`, as for a message from that
channel, or, when `account_id` is `cron:<user ID>` (as cron agent jobs send it),
with that user's role. With
`"channel": "web"` it runs in `admin-session` as the admin (role `system`).
With `"subject_ref": "<user ID>"` (a cron `agent …` job with a subject sends
it) the message runs as that person instead, at role `user` with a
delegation, on every channel, `web` included: `account_id`, the peer's pairing
and `memory.write_access` do not set its role, so directives are refused. Its
session is `p2p:<peer_id>:<default agent>:for:<user ID>`.
`goclaw message <channel> <text>` posts here. A message sent while an earlier one
in the same session is still being answered waits for it; if the request ends
while it waits, the answer is `409`. Reset triggers, directives and `/subagents`
commands work as in [`POST /api/chat`](#post-apichat), except that off the `web`
channel `@config:`, `@notes:` and `@sop:` need that role to be `admin`.
`goclaw message <channel>` sends as peer `cli`, which is paired with no one, so
a directive goes through it only on `web` (the default). An agent error is
answered `500` with the error text.

### Webhook routes API

`/api/webhooks` needs the gateway token (`X-Goclaw-Token: <token>` or
`Authorization: Bearer <token>`).

`GET` lists every route: the stored ones, and the `config.yaml` ones with
`"source": "config"` and `"id": 0`, which cannot be changed here. Secrets are
masked; `secret_set` says whether the route has a secret or token at all.
`hit_count`, `last_hit_at` and `last_error` are kept for stored routes only.
`last_error` is the latest refusal or drop (its time, status and why: a
delivery refused, or taken and then dropped unrun); the next delivery that gets
through clears it.

`POST` takes a JSON body with an `action`:

```bash
curl -X POST https://<gateway>/api/webhooks \
  -H "Authorization: Bearer $GOCLAW_TOKEN" -H "Content-Type: application/json" \
  -d '{"action":"create","type":"graylog","key":"payments-errors","agent":"main","sop_id":"incident-triage"}'
```

```json
{"ok": true, "id": 7, "agent": "main", "generated_secret": "whsec_…"}
```

| Field | Actions | Meaning |
|---|---|---|
| `action` | all | `create`, `update`, `enable`, `disable` or `delete`. |
| `id` | `update`, `enable`, `disable`, `delete` | The stored route's `id` from `GET`. |
| `type` | `create` (required), `update` | First path segment of `/goclaw/hook/<type>/<key>`. On `update`, empty keeps the stored type. |
| `key` | `create`, `update` | Second path segment. Empty makes the type's route for any key that has no route of its own. |
| `agent` | `create`, `update` (required) | Agent ID or name, any case, or `*` for every agent. An unknown agent is refused with `400`, naming the agents there are. |
| `secret`, `token` | `create`, `update` | On `create`, leave both out to have a `whsec_…` secret generated: it is returned once, as `generated_secret`, and not shown again. On `update`, an empty value keeps the stored one. |
| `events` | `create`, `update` | Comma-separated event names, e.g. `"push,merge_request"`; empty takes every event. |
| `sop_id`, `batch_mode`, `batch_window` | `create`, `update` | Run a SOP per event, or per batch. On `update`, an empty `batch_mode` or a `batch_window` of `0` keeps the stored value. |

The fields mean what they mean in `config.yaml`: see
[Webhook Routes](../user-guide/configuration.md#webhook-routes).

- `update` writes `type`, `key`, `agent`, `events` and `sop_id` as sent, so send
  the route's current values along with the change: an empty `key`, `events` or
  `sop_id` clears it. It does not change whether the route is paused.
- `disable` pauses a route: its deliveries are answered `404`, and each is noted
  on the route. `enable` resumes it.
- `delete` removes the route and what was set up for it on the sender's side: the
  GitLab project hook `goclaw gitlab hook` registered, or the Graylog event
  definition and notification `graylog_alert` created. The response's `notice`
  says what was removed, or what is left to delete by hand.

### Webhook receiver

`POST /goclaw/hook/<type>[/<key>]` is where outside systems deliver events. It is
served on the webhook listener: `gateway.ingress.port` when that is set, else the
main port. The route is the one for this type and exact key, else the type's route
without a key; between a stored route and a `config.yaml` route for the same key,
the stored one. How each type authenticates (GitLab token, GitHub HMAC, otherwise
`X-Token`, `?token=` or an `X-Signature` HMAC) is in
[Webhook Routes](../user-guide/configuration.md#webhook-routes).

| Status | When |
|---|---|
| `200` `{"status":"ok", …}` | The event was taken: a SOP run started (or the event queued, for a bulk route or behind the run in progress for the same object), or the agent will answer it. `dispatched` counts the agents it reached. |
| `200` `{"status":"filtered"}` | The route's `events` leave this event out. Nothing runs. |
| `200` `{"status":"duplicate"}` | The route took a delivery with this ID before (a retry, a redelivery). Nothing runs again. |
| `200` `{"status":"test"}` | Graylog's test notification, on a `graylog` route. Nothing runs. |
| `401` | The credential is missing or does not match, or the route has none; the body says which. |
| `403` | The sender's IP is not on `gateway.ingress.allow_ips` / `allow_preset` (only when an allowlist is set; loopback is always let through). |
| `404` | No route for the type and key, or the route is paused. |
| `409` | The route's SOP cannot run: it was deleted or disabled, or it is overdue for review. |
| `413` | The body is larger than 4 MB. |
| `429` | The sender's IP is over `gateway.rate_limit`. |
| `500` | The event was not taken, for example the SOP run could not be started. Send it again. |
| `503` | Too many deliveries are in progress, or 500 events already wait for the same object's run (`Retry-After: 30`), or another request is taking this same delivery right now (`Retry-After: 10`); sent again, it is a repeat or it runs. Also when the delivery's ID could not be recorded (`Retry-After: 10`): run unrecorded, a resend could not be told from it. |

A refused delivery to a stored route is noted on the route (`last_error`, and on
the Webhooks page); for a `config.yaml` route, only the gateway log has it
(`[webhook]` lines). The `503` for a delivery another request is taking is only
in the gateway log: sent again, that delivery is a repeat or runs, as the other
request ended.

## Media and Canvas

### `POST /api/media/upload`

Uploads a file, sent as the multipart form field `file`, to the media store
(`<state dir>/media`) and returns an ID, URL, filename, MIME type, size, and
creation time.

### `GET|HEAD /media/<id>`

Serves a media-store object. Images, audio, video, PDF and plain text are served
inline with their stored content type; anything else (HTML, SVG, XML, scripts,
unknown types) is served as a download (`application/octet-stream`).

### `POST /api/upload-workspace`

Uploads a file, sent as the multipart form field `file`, directly into
`workspace/uploads`. Web chat uses this endpoint so tools can access uploaded
files by workspace-relative path.

The response includes:

```json
{
  "id": "timestamp-filename.png",
  "local_path": "uploads/timestamp-filename.png",
  "filename": "filename.png",
  "url": "/uploads/timestamp-filename.png",
  "mime_type": "image/png",
  "size": 12345
}
```

### `GET /uploads/<file>`

Serves files from `workspace/uploads`. Images, audio, video, PDF, `.txt` and
`.log` files are served inline; any other file, or one without an extension, is
served as a download.

### `GET /api/canvas`

Lists canvas artifacts, each as `{name, size, mod_time}`.

### `GET /canvas/<file>`

Serves generated canvas artifacts from `<state dir>/canvas`, inline or as a
download by the same rule as `/uploads/`. Artifacts are removed after 48 hours.

### `GET /goclaw/view/<slug>`

Serves an HTML page the agent made (`generate_html`). It needs the page's view
token (`?token=<view_token>` or an `X-View-Token` header) or the gateway token.
`/goclaw/view/` without a slug lists the pages and needs the gateway token.

The page is served sandboxed (`Content-Security-Policy: sandbox allow-scripts
…`, without `allow-same-origin`), so the browser treats it as an origin of its
own: its scripts run, but no dashboard cookie goes with its requests, and it
cannot post forms, open frames, use `fetch`, or read `localStorage` or
`document.cookie`.

## Messaging

### `POST /api/send`

Sends a message to a connected channel, without the agent:

```json
{"channel": "discord", "target": "<channel or user ID>", "text": "Hello"}
```

All three fields are required. It needs the gateway token and accepts only
requests from a loopback address (`127.0.0.1`, `::1`); any other is answered
`403`. A channel that is not connected is answered `404`, with the connected
channels in `available`.

## Token Metrics

### `GET /api/metrics/tokens`

Returns token usage summaries. Optional filters include `agent_id`, `channel`,
`provider`, `model`, `date_from`, `date_to`, and `limit` (1 to `500`, default
`50`).

## Traces and Logs

| Endpoint | Purpose |
|---|---|
| `GET /api/traces` | Agent traces, newest first. Filters: `session_id`, `agent_id`, `channel`, `status`, `tag`, `sop_id`, `from` and `to` (unix seconds, RFC 3339 or `YYYY-MM-DD`), `min_tokens` (prompt and output together), `before` (unix seconds, for paging) and `limit` (default `50`, at most `200`). `sort=tokens` lists the traces that used the most tokens first, paged by `offset` instead of `before`. A SOP run's trace is filed under the agent that ran it; `sop_id` finds a SOP's runs. A scheduled built-in or shell command, or a schedule whose SOP is gone, has no agent: find its job's runs by `session_id` (`cron:<job id>`); `GET /api/traces/<id>` also gives the job as `cron_id`. |
| `GET /api/traces/stats` | Trace counts (total, done, error) and averages of duration, tool calls and tokens. |
| `GET /api/traces/<id>` | One trace with its steps, tool calls and timings. |
| `GET /api/traces/<id>.csv` | The same trace as a CSV download. |
| `POST /api/traces/<id>/feedback` | `{"score": 1 or -1, "comment", "user_id"}`: rate a trace. |
| `GET /api/logs` | The gateway log as Server-Sent Events, one line per event. |

## Other Routes

| Endpoint | Purpose |
|---|---|
| `GET /api/sbom`, `GET /api/admin/sbom` | The software bill of materials: `?format=cyclonedx` (default), `spdx` or `summary`; `?download=1` sends it as a file. See [SBOM](../architecture/sbom.md). |
| Web UI only: `/api/agents/profiles`, `/api/agents/profiles/draft`, `/api/agents/instructions`, `/api/agents/instructions/draft`, `/api/agents/persona/draft`, `/api/agents/constraints`, `/api/agents/preview`, `/api/hooks/rules`, `/api/plugins/{inspect,install,agents,approve,inputs,fetch,install-fetched,marketplaces,marketplaces/browse,check-updates,export}`, `/api/memory/wiki` and `/api/memory/wiki/{history,restore,distill,attachments,search}` | The Agents, Instructions, Hooks, Plugins and Wiki pages of the Web UI. Their request shapes follow those pages and are not described here. They need the gateway token like the rest. |

## App API

`POST /api/app/chat` and `GET /api/app/events` serve the Enterprise App API
channel. They check the app's own credential, not the gateway token, and
`/ws/app` answers `410 Gone`. See
[App API](../user-guide/channels/app-api.md).

## OpenAI Compatibility

### `POST /v1/chat/completions`

OpenAI-compatible chat completions endpoint. See
[OpenAI Compatibility](./openai_compatibility.md).

## Health Probes

### `GET /health`, `GET /healthz`, `GET /ready`, `GET /readyz`

Lightweight health probes that return `{"status":"ok","uptime_seconds":…}`.
These endpoints are intentionally unauthenticated. When `gateway.ingress.port`
gives webhooks their own listener, they are served there too.
