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

# REST API

> HTTP server for running and managing agent sessions over REST endpoints.

Meerkat ships a REST server for running and managing agent sessions over HTTP. This is the best fit if you want a simple, language-agnostic API for the Meerkat core.

## Getting started

<Steps>
  <Step title="Start the server">
    <CodeGroup>
      ```bash Installed binary theme={null}
      rkat-rest --realm team-alpha
      ```
    </CodeGroup>
  </Step>

  <Step title="Configure provider credentials">
    For the simplest environment-backed setup, export the key for your chosen
    provider. Realm auth profiles and bindings are the persistent alternative:

    ```bash theme={null}
    export ANTHROPIC_API_KEY="sk-..."
    # or OPENAI_API_KEY, GOOGLE_API_KEY
    ```
  </Step>

  <Step title="Send a request">
    ```bash theme={null}
    curl -X POST http://127.0.0.1:8080/sessions \
      -H "Content-Type: application/json" \
      -d '{"prompt": "Hello, Meerkat!"}'
    ```
  </Step>
</Steps>

## Runtime scope

`rkat-rest` accepts:

* `--realm <id>`
* `--isolated`
* `--instance <id>`
* `--realm-backend <sqlite|jsonl|memory>`
* `--state-root <path>`
* `--context-root <path>`
* `--user-config-root <path>`
* `--expose-paths`

If `--realm` is omitted, the server creates a new isolated opaque realm (`realm-...`).
`--realm-backend` is a creation hint only; after first open, `realm_manifest.json`
is authoritative.

<Warning>
  The REST server has no general inbound HTTP authentication layer. The `auth/*`
  routes manage outbound provider credentials; they do not authenticate callers
  to this API. Keep the listener on loopback or put it behind an authenticated,
  encrypted reverse proxy. The webhook-secret check applies only to the external
  event route.
</Warning>

## Endpoint overview

This overview follows the generated OpenAPI artifact at
`artifacts/schemas/rest-openapi.json`.

| Method   | Path                                         | Description                                                              |
| -------- | -------------------------------------------- | ------------------------------------------------------------------------ |
| `POST`   | `/help`                                      | Ask Meerkat usage help with the embedded platform skill                  |
| `POST`   | `/sessions`                                  | Create and run a new session                                             |
| `GET`    | `/sessions`                                  | List sessions                                                            |
| `GET`    | `/sessions/{id}`                             | Fetch session metadata and usage                                         |
| `GET`    | `/sessions/{id}/status`                      | Fetch current runtime state for a session                                |
| `GET`    | `/sessions/{id}/history`                     | Fetch committed session transcript messages                              |
| `POST`   | `/sessions/{id}/system_context`              | Append one ordinary durable ordered System message                       |
| `POST`   | `/sessions/{id}/system_prompt`               | Replace one durable versioned system-prompt key                          |
| `POST`   | `/sessions/{id}/messages`                    | Continue an existing session                                             |
| `POST`   | `/sessions/{id}/external-events`             | Queue a runtime-backed external event                                    |
| `POST`   | `/sessions/{id}/peer-response-terminal`      | Queue a typed peer terminal response event                               |
| `POST`   | `/sessions/{id}/interrupt`                   | Interrupt an in-flight turn                                              |
| `DELETE` | `/sessions/{id}`                             | Archive (remove) a session                                               |
| `GET`    | `/sessions/{id}/events`                      | SSE stream for real-time updates                                         |
| `GET`    | `/schedule/tools`                            | List schedule tool definitions                                           |
| `POST`   | `/schedule/call`                             | Call a schedule tool directly                                            |
| `POST`   | `/schedules`                                 | Create a schedule                                                        |
| `GET`    | `/schedules`                                 | List schedules                                                           |
| `GET`    | `/schedules/{id}`                            | Fetch one schedule                                                       |
| `PATCH`  | `/schedules/{id}`                            | Update a schedule                                                        |
| `DELETE` | `/schedules/{id}`                            | Delete a schedule                                                        |
| `POST`   | `/schedules/{id}/pause`                      | Pause a schedule                                                         |
| `POST`   | `/schedules/{id}/resume`                     | Resume a schedule                                                        |
| `GET`    | `/schedules/{id}/occurrences`                | List schedule occurrences                                                |
| `GET`    | `/workgraph/items`                           | List WorkGraph items                                                     |
| `GET`    | `/workgraph/items/{id}`                      | Fetch one WorkGraph item                                                 |
| `GET`    | `/workgraph/ready`                           | List ready WorkGraph items                                               |
| `GET`    | `/workgraph/snapshot`                        | Read a WorkGraph observability snapshot                                  |
| `GET`    | `/workgraph/events`                          | Read WorkGraph event history                                             |
| `POST`   | `/workgraph/goal/status`                     | Read goal and attention status                                           |
| `POST`   | `/workgraph/attention/list`                  | List attention bindings                                                  |
| `POST`   | `/sessions/{id}/mcp/add`                     | Stage live MCP server addition (mcp feature)                             |
| `POST`   | `/sessions/{id}/mcp/remove`                  | Stage live MCP server removal (mcp feature)                              |
| `POST`   | `/sessions/{id}/mcp/reload`                  | Reload MCP server(s) (mcp feature)                                       |
| `POST`   | `/comms/send`                                | Push comms message into a session (comms feature)                        |
| `GET`    | `/comms/peers`                               | List discoverable peers (comms feature)                                  |
| `GET`    | `/auth/profiles`                             | List realm auth profiles, backend profiles, and bindings                 |
| `POST`   | `/auth/profiles`                             | Store binding-scoped credentials                                         |
| `GET`    | `/auth/bindings/{binding_id}`                | Read a binding-scoped auth profile                                       |
| `DELETE` | `/auth/bindings/{binding_id}`                | Delete binding-scoped credentials                                        |
| `POST`   | `/auth/bindings/{binding_id}/test`           | Test a binding resolve path                                              |
| `POST`   | `/auth/login/start`                          | Start interactive auth login                                             |
| `POST`   | `/auth/login/complete`                       | Complete interactive auth login                                          |
| `POST`   | `/auth/login/device/start`                   | Start device-code login                                                  |
| `POST`   | `/auth/login/device/complete`                | Complete device-code login                                               |
| `GET`    | `/auth/bindings/{binding_id}/status`         | Read binding auth status                                                 |
| `POST`   | `/auth/bindings/{binding_id}/logout`         | Log out a binding                                                        |
| `GET`    | `/realms`                                    | List configured realms                                                   |
| `GET`    | `/realms/{id}`                               | Read one realm                                                           |
| `GET`    | `/mob/{id}/events`                           | Mob event SSE stream (mob feature)                                       |
| `POST`   | `/mob/{id}/spawn-helper`                     | Spawn a helper into a mob (mob feature)                                  |
| `POST`   | `/mob/{id}/fork-helper`                      | Fork a helper from an existing member (mob feature)                      |
| `POST`   | `/mob/{id}/wait-kickoff`                     | Wait for kickoff completion (mob feature)                                |
| `POST`   | `/mob/{id}/wire-members-batch`               | Wire multiple mob members in one request (mob feature)                   |
| `GET`    | `/mob/{id}/members/{agent_identity}/status`  | Read member status (mob feature)                                         |
| `POST`   | `/mob/{id}/members/{agent_identity}/cancel`  | Force-cancel a member (mob feature)                                      |
| `POST`   | `/mob/{id}/members/{agent_identity}/respawn` | Respawn a member (mob feature)                                           |
| `GET`    | `/mob/{id}/members/{agent_identity}/history` | Read a mob member transcript page by identity (mob feature)              |
| `GET`    | `/mob/{id}/hosts`                            | List tracked member hosts with bind phase and capabilities (mob feature) |
| `GET`    | `/mob/{id}/route-installs`                   | Outstanding cross-host route-install obligations (mob feature)           |
| `GET`    | `/skills`                                    | List skills with provenance                                              |
| `GET`    | `/health`                                    | Liveness check                                                           |
| `GET`    | `/runtime/host_info`                         | Read runtime host identity, endpoints, and realm projection              |
| `GET`    | `/runtime/capabilities`                      | Read runtime host capability flags                                       |
| `GET`    | `/runtime/health`                            | Read runtime host health                                                 |
| `GET`    | `/models/catalog`                            | Curated model catalog with provider profiles                             |
| `GET`    | `/capabilities`                              | Runtime capabilities                                                     |
| `GET`    | `/config`                                    | Read config                                                              |
| `PUT`    | `/config`                                    | Replace config                                                           |
| `PATCH`  | `/config`                                    | Merge-patch config (RFC 7396)                                            |
| `POST`   | `/requests/{request_id}/cancel`              | Cancel an uncommitted in-flight request                                  |

Helper requests (`/mob/{id}/spawn-helper`, `/mob/{id}/fork-helper`) require
`result_label` and `max_text_bytes`. Spawn and fork responses are
exact-operation results with required `output`, `tokens_used`,
`agent_identity`, `member_ref`, `bounded_result`, `session_id`, `usage`,
`turns`, and `tool_calls`; `retirement_error` is present only when cleanup
debt remains after the certified result was captured.

<Note>
  Schedule endpoints return the same flattened public `Schedule` contract as
  JSON-RPC. Planning, labels, and created/updated timestamps are top-level;
  there is no nested `config` and persisted `machine_state` is not public.
</Note>

WorkGraph goal and attention mutation routes require trusted in-process
host/session authority and are not part of the public REST catalog.

Approvals, durable jobs and monitors, stable artifact record/download methods,
and low-latency live channels are not REST routes. Those operator surfaces are
available through JSON-RPC and the generated SDKs. REST history can carry blob
references, but REST does not expose blob or artifact download paths.

Multi-host mob administration (host bind/revoke, hard-cancel, member live
channels, grant management) is deliberately NOT served by REST — REST carries
observation GETs only. The complete admin family lives on JSON-RPC and the
Python/TypeScript SDKs; host binding/revoke, grants, and live control also have
CLI verbs, while hard cancel is RPC/SDK-only.

Durable member role migration is also outside the public REST contract. The
shared OpenAPI component catalog includes private member-host bridge schemas,
including `MaterializeLaunchMode`, because components are generated from shared
wire types. No REST path accepts `resume_from_role`; only a trusted Rust host or
the private member-host materialization protocol can issue that one-shot
declaration. The `paths` object, not component reachability, defines REST
exposure.

<Note>
  Generated images are not returned as inline bytes in history. `GET /sessions/{id}/history` returns assistant image blocks with `image_id`, `blob_ref`, dimensions, and metadata; fetch image bytes through the blob/artifact surface exposed by the runtime-backed RPC/SDK path.
</Note>

<Note>
  REST keeps observation and helper endpoints for mobs, but typed lifecycle/control
  for app hosts lives on the canonical RPC/SDK `mob/*` surface. Inside running
  sessions, mob capability is still exposed by composing `meerkat-mob-mcp`
  (`MobMcpState` + `AgentMobToolSurfaceFactory`) into
  `SessionBuildOptions.mob_tools` in the host runtime. `external_tools` remains
  reserved for callback and MCP-backed dispatchers.
</Note>

<Note>
  WorkGraph REST endpoints are observability/operator lookup, including
  goal-status and attention-list reads. Agents create, claim, update, link,
  evidence, close, reassign attention, and escalate policy through WorkGraph tools
  inside sessions; goal and attention mutators stay on trusted in-process host
  authority.
</Note>

## Request cancellation

REST request cancellation is opt-in and request-ID based.

* send `X-Meerkat-Request-Id: <id>` on `POST /sessions` or `POST /sessions/{id}/messages`
* call `POST /requests/{request_id}/cancel` to cancel uncommitted in-flight work
* duplicate in-flight request IDs are rejected

Cancellation only affects uncommitted work:

* pre-start / pre-commit work may return cancelled
* committed success is not rewritten to cancellation
* post-commit create failures still return session identity and remain resumable

## Configuration

<Accordion title="Server configuration details">
  REST configuration is realm-scoped:

  * macOS: `~/Library/Application Support/meerkat/realms/<realm>/config.toml`
  * Linux: `~/.local/share/meerkat/realms/<realm>/config.toml`
  * Windows: `%APPDATA%\\meerkat\\realms\\<realm>\\config.toml`

  Each realm also has `realm_manifest.json` (backend pinning) and `config_state.json` (generation CAS state).

  Key sections:

  ```toml theme={null}
  [rest]
  host = "127.0.0.1"
  port = 8080

  [agent]
  model = "claude-opus-4-8"
  max_tokens_per_turn = 8192

  [tools]
  builtins_enabled = false
  shell_enabled = false
  schedule_enabled = true
  workgraph_enabled = false
  ```

  API keys are provided via environment variables:

  * `ANTHROPIC_API_KEY`
  * `OPENAI_API_KEY`
  * `GOOGLE_API_KEY`
</Accordion>

## Endpoints

### POST /help

Ask Meerkat usage help with the embedded platform skill.

```json Request theme={null}
{
  "question": "How do I add an MCP server?"
}
```

Returns a `HelpResponse` with the answer text and any plan metadata requested by the input.

### POST /sessions

Create and run a new session.

<CodeGroup>
  ```json Request (minimal) theme={null}
  {
    "prompt": "Your prompt here"
  }
  ```

  ```json Request (full) theme={null}
  {
    "prompt": "Your prompt here",
    "system_prompt": "Optional system prompt",
    "model": "claude-opus-4-8",
    "provider": "anthropic",
    "max_tokens": 4096,
    "output_schema": {
      "schema": {"type": "object", "properties": {"answer": {"type": "string"}}},
      "name": "answer",
      "strict": false,
      "compat": "lossy",
      "format": "meerkat_v1"
    },
    "structured_output_retries": 2,
    "verbose": false,
    "keep_alive": null,
    "comms_name": null,
    "peer_meta": null,
    "hooks_override": null,
    "enable_builtins": null,
    "enable_shell": null,
    "enable_memory": null,
    "enable_schedule": null,
    "enable_workgraph": null
  }
  ```

  ```json Response theme={null}
  {
    "session_id": "01936f8a-7b2c-7000-8000-000000000001",
    "text": "Response text",
    "turns": 1,
    "tool_calls": 0,
    "usage": {
      "input_tokens": 50,
      "output_tokens": 200,
      "total_tokens": 250
    },
    "structured_output": null,
    "schema_warnings": null
  }
  ```
</CodeGroup>

#### Request fields

<ParamField body="prompt" type="string" required>
  The user prompt to send to the agent.
</ParamField>

<ParamField body="injected_context" type="ContentInput[] | null" default="null">
  Host-attached injected context for the first turn. Each entry materializes
  as a separate typed injected-context user-channel message immediately before
  the first turn's user message, in order; injected context is excluded from
  semantic-memory indexing. Older REST servers silently ignore this field
  (existing REST version-skew posture).
</ParamField>

<ParamField body="transient_turn_context" type="string | null" default="null">
  Non-empty, exact host-regenerated facts for the first logical turn.
  Whitespace is significant. Meerkat persists the text only with the pending
  runtime input for crash retry and projects it as a request-local user-channel
  context message immediately before that turn's conversational user message.
  It never enters Session history, compaction, or extraction requests.
</ParamField>

<ParamField body="system_prompt" type="SystemPromptOverride" default="inherit">
  Typed per-request system-prompt policy: omit/`null` to inherit, a string to
  set an explicit prompt, or `{"action": "disable"}` to suppress every prompt
  source.
</ParamField>

<ParamField body="model" type="string | null" default="config default">
  Model name (e.g. `"claude-opus-4-8"`, `"gpt-5.5"`).
</ParamField>

<ParamField body="provider" type="string | null" default="inferred from model">
  Provider: `"anthropic"`, `"openai"`, `"gemini"`, `"self_hosted"`, `"other"`.
</ParamField>

<ParamField body="max_tokens" type="u32 | null" default="config default">
  Max tokens per turn.
</ParamField>

<ParamField body="output_schema" type="OutputSchema | null" default="null">
  JSON schema for structured output extraction (wrapper or raw schema).
</ParamField>

<ParamField body="structured_output_retries" type="u32 | null" default="null">
  Max retries for structured output validation. `null`/omitted uses the server default on create and inherits the persisted session value on continue.
</ParamField>

<ParamField body="verbose" type="bool" default="false">
  Enable verbose event logging (server-side).
</ParamField>

<ParamField body="keep_alive" type="bool | null" default="null">
  Keep session alive after turn for comms. On create, `null`/omitted uses the create default (`false`), `true` enables, and `false` explicitly disables. Requires `comms_name` when enabled.
</ParamField>

<ParamField body="comms_name" type="string | null" default="null">
  Agent name for inter-agent communication.
</ParamField>

<ParamField body="peer_meta" type="object | null" default="null">
  Friendly metadata for peer discovery (name, description, labels).
</ParamField>

<ParamField body="hooks_override" type="HookRunOverrides | null" default="null">
  Run-scoped hook overrides (see [Hooks](/guides/hooks)).
</ParamField>

<ParamField body="enable_builtins" type="bool | null" default="null (factory default)">
  Enable built-in tools (task management, etc.). Omit to use factory defaults.
</ParamField>

<ParamField body="enable_shell" type="bool | null" default="null (factory default)">
  Enable shell tool (requires `enable_builtins`). Omit to use factory defaults.
</ParamField>

<ParamField body="enable_memory" type="bool | null" default="null (factory default)">
  Enable semantic memory. Omit to use factory defaults.
</ParamField>

<ParamField body="enable_schedule" type="bool | null" default="null (factory default)">
  Override schedule tools for this session. Omit to use factory defaults.
</ParamField>

<ParamField body="enable_workgraph" type="bool | null" default="null (factory default)">
  Override WorkGraph tools for this session. Omit to use factory defaults.
</ParamField>

#### Response fields

<ResponseField name="session_id" type="string">
  UUID of the created session.
</ResponseField>

<ResponseField name="text" type="string">
  The agent's response text.
</ResponseField>

<ResponseField name="turns" type="u32">
  Number of LLM calls made.
</ResponseField>

<ResponseField name="tool_calls" type="u32">
  Number of tool calls executed.
</ResponseField>

<ResponseField name="usage" type="WireUsage">
  Token usage breakdown.
</ResponseField>

<ResponseField name="structured_output" type="object | null">
  Parsed structured output (when `output_schema` was provided).
</ResponseField>

<ResponseField name="schema_warnings" type="array | null">
  Schema compatibility warnings per provider.
</ResponseField>

### GET /sessions/{id}/history

Read committed transcript history for a session without changing the lightweight metadata shape of `GET /sessions/{id}`.

Query parameters:

* `offset` — skip this many messages from the start of the transcript
* `limit` — cap the number of returned messages

```json theme={null}
{
  "session_id": "01936f8a-7b2c-7000-8000-000000000001",
  "message_count": 8,
  "offset": 0,
  "limit": 50,
  "has_more": false,
  "messages": [
    { "role": "system", "content": "You are a helpful assistant." },
    { "role": "user", "content": "Hello" },
    {
      "role": "block_assistant",
      "blocks": [{ "block_type": "text", "data": { "text": "Hi there." } }],
      "stop_reason": "end_turn"
    }
  ]
}
```

History is returned oldest-to-newest and reflects the last committed session snapshot only.

### POST /sessions/\{id}/system\_context

Append one ordinary durable ordered System message at the admitted transcript
boundary. `content` uses the `CoreRenderable` tagged shape, and the optional
`idempotency_key` makes an exact caller retry safe.

```json Request theme={null}
{
  "content": {"type": "text", "text": "Treat the incident as severity one."},
  "source": "incident-controller",
  "idempotency_key": "incident-42-policy-v1"
}
```

```json Response theme={null}
{
  "session_id": "01936f8a-7b2c-7000-8000-000000000001",
  "status": "applied"
}
```

### POST /sessions/\{id}/system\_prompt

Replace one keyed system-prompt slot with version CAS. The first explicit
adoption supplies `target_message_index` for an existing unversioned System row
and omits `expected_version`. Later updates omit the target and provide the
latest `expected_version`. `expected_parent_revision` optionally adds a
transcript-head CAS.

```json Request theme={null}
{
  "key": "operator-policy",
  "expected_version": 1,
  "content": "Escalate critical actions for human approval.",
  "actor": "operator:alice"
}
```

The result contains `session_id`, `key`, the new `version`, `message_index`,
`status` (`applied` or `duplicate`), `transcript_revision`, and an optional
rewrite `commit`. Mob-owned system prompts must be updated through their mob
owner and are rejected on this session route.

### GET /sessions

List sessions. Supports optional label filters via query parameters.

```json Response theme={null}
{
  "sessions": [
    {
      "session_id": "01936f8a-7b2c-7000-8000-000000000001",
      "state": "idle",
      "created_at": "2025-01-15T10:30:00Z"
    }
  ]
}
```

### POST /sessions/\{id}/messages

Continue an existing session.

<CodeGroup>
  ```json Request theme={null}
  {
    "session_id": "01936f8a-7b2c-7000-8000-000000000001",
    "prompt": "Follow-up message"
  }
  ```

  ```json Response theme={null}
  {
    "session_id": "01936f8a-7b2c-7000-8000-000000000001",
    "text": "Response to follow-up",
    "turns": 1,
    "tool_calls": 0,
    "usage": {
      "input_tokens": 100,
      "output_tokens": 150,
      "total_tokens": 250
    },
    "structured_output": null,
    "schema_warnings": null
  }
  ```
</CodeGroup>

#### Request fields

<ParamField body="session_id" type="string" required>
  Session ID (must match the path `{id}`).
</ParamField>

<ParamField body="prompt" type="string" required>
  Follow-up prompt.
</ParamField>

<ParamField body="injected_context" type="ContentInput[] | null" default="null">
  Host-attached injected context for this turn. Each entry materializes as a
  separate typed injected-context user-channel message immediately before the
  turn's user message, in order; injected context is excluded from
  semantic-memory indexing. Older REST servers silently ignore this field
  (existing REST version-skew posture).
</ParamField>

<ParamField body="transient_turn_context" type="string | null" default="null">
  Non-empty, exact host-regenerated facts for this logical turn. Whitespace is
  significant. The value is available to every foreground provider call and
  retry in the turn, persists only while the runtime input is pending, and
  never becomes a Session message.
</ParamField>

<ParamField body="system_prompt" type="string | null" default="null">
  Override the system prompt.
</ParamField>

<ParamField body="model" type="string | null" default="from session">
  Model override for this turn. On materialized sessions this hot-swaps the LLM client for the remainder of the session.
</ParamField>

<ParamField body="provider" type="string | null" default="from session">
  Provider override for this turn. Typically inferred from `model`. Used with `model` for mid-session provider switching.
</ParamField>

<ParamField body="max_tokens" type="u32 | null" default="from session">
  Max tokens override for this turn.
</ParamField>

<ParamField body="output_schema" type="OutputSchema | null" default="null">
  Structured output schema for this turn.
</ParamField>

<ParamField body="structured_output_retries" type="u32 | null" default="null">
  Max retries for structured output validation. Omit / `null` to inherit the persisted session value on continue.
</ParamField>

<ParamField body="verbose" type="bool" default="false">
  Enable verbose event logging.
</ParamField>

<ParamField body="keep_alive" type="bool | null" default="null">
  Keep-alive override for this turn. `null` = inherit persisted session intent, `true` = enable, `false` = disable.
</ParamField>

<ParamField body="comms_name" type="string | null" default="from session">
  Agent name for comms.
</ParamField>

<ParamField body="hooks_override" type="HookRunOverrides | null" default="null">
  Run-scoped hook overrides.
</ParamField>

#### Response fields

Same shape as `POST /sessions`.

### POST /sessions/\{id}/interrupt

Interrupt an in-flight turn. No-op if the session is idle.

```json Response theme={null}
{"interrupted": true}
```

### POST /requests/\{request\_id}/cancel

Cancel an uncommitted in-flight request that previously supplied `X-Meerkat-Request-Id`.

```json Response theme={null}
{"cancelled": true}
```

Returns `200` with `{"cancelled": false, "reason": "already_terminal"}` when
the tracked request already published or completed. Returns `404` only when the
request ID is unknown.

### DELETE /sessions/\{id}

Archive (remove) a session.

```json Response theme={null}
{"archived": true}
```

Returns `404` if the session is not found.

### POST /sessions/\{id}/external-events

Queue an external event through the runtime-backed admission path.

<CodeGroup>
  ```bash Without auth (localhost only) theme={null}
  curl -X POST http://localhost:8080/sessions/sid_abc/external-events \
    -H "Content-Type: application/json" \
    -d '{"alert": "CPU spike", "host": "web-03"}'
  ```

  ```bash With webhook secret theme={null}
  curl -X POST http://localhost:8080/sessions/sid_abc/external-events \
    -H "Content-Type: application/json" \
    -H "X-Webhook-Secret: my-secret" \
    -d '{"alert": "deployment failed"}'
  ```

  ```json Response (202 Accepted) theme={null}
  {"queued": true}
  ```
</CodeGroup>

<Note>
  This route keeps the optional `RKAT_WEBHOOK_SECRET` header auth used for webhook
  delivery, but the admitted event now flows through runtime input acceptance
  instead of a surface-local injector path.
</Note>

<ParamField body="(body)" type="any JSON" required>
  Any JSON payload. Pretty-printed and injected as an event into the agent's inbox.
</ParamField>

<ParamField header="X-Webhook-Secret" type="string">
  Webhook secret for authentication. Required when `RKAT_WEBHOOK_SECRET` env var is set on the server. Compared using constant-time equality (`subtle::ConstantTimeEq`).
</ParamField>

| HTTP Status | When                              |
| ----------- | --------------------------------- |
| 202         | Event queued successfully         |
| 404         | Session not found                 |
| 401         | Missing or invalid webhook secret |
| 503         | Event inbox is full               |

### POST /sessions/\{id}/peer-response-terminal

Admit a terminal peer response through the typed runtime ingress.

```bash theme={null}
curl -X POST http://localhost:8080/sessions/sid_abc/peer-response-terminal \
  -H "Content-Type: application/json" \
  -d '{
    "peer_id": "00000000-0000-4000-8000-000000000161",
    "display_name": "analyst",
    "request_id": "00000000-0000-4000-8000-000000000162",
    "status": "completed",
    "result": {"token": "silver harbor"}
  }'
```

<ParamField body="peer_id" type="string" required>
  Canonical peer routing ID. Display names are not accepted as routing identity.
</ParamField>

<ParamField body="display_name" type="string">
  Optional presentation label.
</ParamField>

<ParamField body="request_id" type="string" required>
  Peer correlation ID for the request this terminal response completes.
</ParamField>

<ParamField body="status" type="string" required>
  Terminal response status: `"completed"`, `"failed"`, or `"cancelled"`.
</ParamField>

<ParamField body="result" type="any JSON" required>
  Peer-returned terminal payload.
</ParamField>

### GET /sessions/\{id}

Fetch session metadata and usage.

```json Response theme={null}
{
  "session_id": "01936f8a-7b2c-7000-8000-000000000001",
  "created_at": "2025-01-15T10:30:00Z",
  "updated_at": "2025-01-15T10:31:00Z",
  "message_count": 4,
  "total_tokens": 500
}
```

### GET /sessions/\{id}/events

Server-Sent Events (SSE) stream for real-time updates.

Event types:

| Event                      | Description                                                             |
| -------------------------- | ----------------------------------------------------------------------- |
| `session_loaded`           | Emitted on connect with session metadata                                |
| `run_started`              | Agent execution began                                                   |
| `run_completed`            | Agent run finished                                                      |
| `run_failed`               | Agent run failed                                                        |
| `turn_started`             | New LLM call within the turn                                            |
| `text_delta`               | Streaming text chunk from LLM                                           |
| `text_complete`            | Full text for this turn                                                 |
| `tool_call_requested`      | LLM wants to call a tool                                                |
| `tool_result_received`     | Tool result processed                                                   |
| `turn_completed`           | LLM call finished                                                       |
| `tool_execution_started`   | Tool dispatch began                                                     |
| `tool_execution_completed` | Tool returned a result                                                  |
| `tool_execution_timed_out` | Tool exceeded timeout                                                   |
| `compaction_started`       | Context compaction began                                                |
| `compaction_completed`     | Compaction finished                                                     |
| `compaction_failed`        | Compaction failed                                                       |
| `budget_warning`           | Approaching resource limits                                             |
| `retrying`                 | Retrying after transient error                                          |
| `hook_started`             | Hook execution began                                                    |
| `hook_completed`           | Hook finished                                                           |
| `hook_failed`              | Hook execution failed                                                   |
| `hook_denied`              | Hook blocked operation                                                  |
| `skills_resolved`          | Skills loaded for turn                                                  |
| `skill_resolution_failed`  | Skill resolution failed                                                 |
| `stream_truncated`         | The bounded live subscriber lagged; the event carries the dropped count |
| `done`                     | Emitted when the broadcast channel closes                               |

The live SSE subscription is bounded and best-effort. A slow subscriber gets a
typed `stream_truncated` marker and then retained live events. Built-in
realm-backed persistent sessions can feed an optional durable audit projector
through a separate unbounded queue, so SSE lag does not drop its input. The
audit log is asynchronous derived state. Reconnect and reconcile from session
history/status, or use the JSON-RPC `events/*` methods when that replay surface
is healthy.

### GET /skills

List all skills with provenance information. Returns active and shadowed entries.

```json Response theme={null}
{
  "skills": [
    {
      "key": {
        "source_uuid": "00000000-0000-4b11-8111-000000000001",
        "skill_name": "task-workflow"
      },
      "name": "Task Workflow",
      "description": "How to use task tools",
      "scope": "builtin",
      "source": {
        "source_uuid": "00000000-0000-4b11-8111-000000000001",
        "display_name": "embedded",
        "transport_kind": "embedded",
        "fingerprint": "embedded:inventory",
        "status": "active"
      },
      "is_active": true
    },
    {
      "key": {
        "source_uuid": "dc256086-0d2f-4f61-a307-320d4148107f",
        "skill_name": "task-workflow"
      },
      "name": "Custom Task Workflow",
      "description": "Override tasks",
      "scope": "project",
      "source": {
        "source_uuid": "dc256086-0d2f-4f61-a307-320d4148107f",
        "display_name": "company",
        "transport_kind": "git",
        "fingerprint": "repo-7cc66f36fd9db1a1",
        "status": "active"
      },
      "is_active": true
    }
  ]
}
```

Returns `404` if skills are not enabled.

### GET /health

Returns `"ok"` (HTTP 200). Use for liveness checks.

### GET /runtime/host\_info

Returns the read-only `RuntimeHostInfo` projection: process identity, host ID
scope, realm and endpoint metadata, feature flags, and the same health payload
as `GET /runtime/health`. It does not enroll a host or grant placement
authority.

### GET /runtime/capabilities

Returns `RuntimeHostCapabilities`, a contract version plus boolean
`RuntimeHostFeatureFlags`. These host feature flags are distinct from the
generic status entries returned by `GET /capabilities`.

### GET /runtime/health

Returns `RuntimeHostHealth` with `status` and a `checks` map. The declared
dimensions are `jobs`, `session_liveness`, `session_durability`,
`session_runtime_loop`, and `session_run_start`. REST measures the four session
dimensions. It has no detached-job service probe, so it reports
`unmeasured:jobs` as a coverage marker and leaves that marker out of the status
rollup.

A plain dimension key is a measured result. `unreadable:<dimension>` means the
probe ran but could not obtain a reading and rolls the overall status up to at
least `degraded`. `unmeasured:<dimension>` means this surface has no probe and
does not by itself make a healthy host permanently degraded.

### GET /models/catalog

Return the curated model catalog with provider profiles, capability metadata, and parameter schemas.

```json Response (abbreviated) theme={null}
{
  "contract_version": "0.8.33",
  "providers": [
    {
      "provider": "anthropic",
      "default_model_id": "claude-opus-5",
      "models": [
        {
          "id": "claude-opus-5",
          "display_name": "Claude Opus 5",
          "tier": "recommended",
          "context_window": 1000000,
          "max_output_tokens": 128000,
          "profile": {
            "model_family": "claude-opus-5",
            "supports_temperature": false,
            "supports_thinking": true,
            "supports_reasoning": false,
            "params_schema": {}
          }
        }
      ]
    }
  ]
}
```

The catalog is resolved from built-in model metadata plus config-backed provider/server entries. Each provider entry includes the default model and a list of models with their capabilities and parameter schemas.

### GET /capabilities

Returns runtime capabilities with status resolved against config.

```json Response (abbreviated) theme={null}
{
  "contract_version": {"major": 0, "minor": 8, "patch": 33},
  "capabilities": [
    {
      "id": "sessions",
      "description": "Session lifecycle management",
      "status": "Available"
    },
    {
      "id": "shell",
      "description": "Shell command execution",
      "status": {"DisabledByPolicy": {"description": "Disabled by config"}}
    }
  ]
}
```

The full response includes all registered capabilities from the running build,
including `sessions`, `streaming`, `structured_output`, `hooks`, `builtins`,
`shell`, `comms`, `memory_store`, `schedule`, `work_graph`, `session_store`,
`session_compaction`, `skills`, and `mcp_live` when their owning crates are
linked. See the [JSON-RPC API](/api/rpc) for the corresponding RPC surface.

### GET /config

Returns a realm config envelope:

* `config`
* `generation`
* `realm_id`
* `instance_id`
* `backend`
* `resolved_paths`

### PUT /config

Replaces config.

Accepted request forms:

* Direct config object (compat)
* Wrapped form with CAS:
  * `{ "config": <Config>, "expected_generation": <u64|null> }`

### PATCH /config

Applies RFC 7396 merge patch.

Accepted request forms:

* Direct patch object (compat)
* Wrapped form with CAS:
  * `{ "patch": <JSON>, "expected_generation": <u64|null> }`

If `expected_generation` is stale, the server returns `400` with a generation-conflict message.

### GET /auth/profiles

List the auth profiles, backend profiles, and provider bindings for a realm.

<ParamField query="realm_id" type="string" required>
  Realm to read.
</ParamField>

<ParamField query="profile_id" type="string | null">
  Optional profile selector accepted by the shared auth query shape.
</ParamField>

```json Response theme={null}
{
  "realm_id": "prod",
  "auth_profiles": [],
  "backend_profiles": [],
  "bindings": []
}
```

### POST /auth/profiles

Store credentials for an existing binding-scoped auth profile. The binding must
resolve to an auth profile whose source is `managed_store`; inline, env,
external resolver, platform-default, command, and file-descriptor sources are
configured outside this endpoint.

```json Request theme={null}
{
  "realm_id": "prod",
  "binding_id": "openai",
  "profile_id": "prod_openai",
  "provider": "openai",
  "auth_method": "api_key",
  "secret": "sk-..."
}
```

```json Response (201 Created) theme={null}
{
  "realm_id": "prod",
  "binding_id": "openai",
  "auth_binding": {
    "realm": "prod",
    "binding": "openai",
    "profile": "prod_openai"
  },
  "profile_id": "prod_openai",
  "provider": "openai",
  "auth_method": "api_key",
  "stored": true
}
```

<ParamField body="realm_id" type="string" required>
  Realm containing the binding.
</ParamField>

<ParamField body="binding_id" type="string" required>
  Binding whose configured auth profile will receive the stored credential.
</ParamField>

<ParamField body="profile_id" type="string | null">
  Optional explicit profile override for the binding.
</ParamField>

<ParamField body="provider" type="string" required>
  Provider expected from the resolved auth profile.
</ParamField>

<ParamField body="auth_method" type="string" required>
  Stored-secret method: `"api_key"`, `"azure_api_key"`, or `"static_bearer"`.
</ParamField>

<ParamField body="secret" type="string" required>
  Secret material to persist in the token store.
</ParamField>

### GET /auth/bindings/\{binding\_id}

Read the auth profile resolved by a binding.

<ParamField path="binding_id" type="string" required>
  Binding to resolve.
</ParamField>

<ParamField query="realm_id" type="string" required>
  Realm containing the binding.
</ParamField>

<ParamField query="profile_id" type="string | null">
  Optional explicit profile override for the binding.
</ParamField>

### DELETE /auth/bindings/\{binding\_id}

Clear stored credentials for the resolved binding-scoped auth profile.

<ParamField path="binding_id" type="string" required>
  Binding whose stored credentials should be cleared.
</ParamField>

<ParamField query="realm_id" type="string" required>
  Realm containing the binding.
</ParamField>

<ParamField query="profile_id" type="string | null">
  Optional explicit profile override for the binding.
</ParamField>

### POST /auth/bindings/\{binding\_id}/test

Resolve a binding through the provider registry and report whether credential
material or a dynamic authorizer is available.

```json Request theme={null}
{
  "realm_id": "prod",
  "profile_id": "prod_openai"
}
```

<ParamField path="binding_id" type="string" required>
  Binding to test.
</ParamField>

<ParamField body="realm_id" type="string" required>
  Realm containing the binding.
</ParamField>

<ParamField body="profile_id" type="string | null">
  Optional explicit profile override for the binding.
</ParamField>

### POST /auth/login/start

Begin a loopback OAuth login. The server owns the OAuth state and PKCE verifier.

```json Request theme={null}
{
  "provider": "anthropic",
  "redirect_uri": "http://127.0.0.1:53682/callback"
}
```

### POST /auth/login/complete

Complete a loopback OAuth login and store the resulting tokens under an explicit
binding-scoped `AuthBindingRef`.

```json Request theme={null}
{
  "provider": "anthropic",
  "code": "provider-code",
  "state": "state-from-start",
  "redirect_uri": "http://127.0.0.1:53682/callback",
  "realm_id": "prod",
  "binding_id": "anthropic",
  "profile_id": "claude_oauth"
}
```

<ParamField body="realm_id" type="string" required>
  Realm containing the binding.
</ParamField>

<ParamField body="binding_id" type="string" required>
  Binding that owns the stored OAuth tokens.
</ParamField>

<ParamField body="profile_id" type="string | null">
  Optional explicit profile override for the binding.
</ParamField>

### POST /auth/login/device/start

Begin a device-code OAuth login.

```json Request theme={null}
{
  "provider": "google"
}
```

### POST /auth/login/device/complete

Poll one device-code OAuth completion attempt and, when ready, store the tokens
under an explicit binding-scoped `AuthBindingRef`.

```json Request theme={null}
{
  "provider": "google",
  "device_code": "device-code-from-start",
  "realm_id": "prod",
  "binding_id": "google",
  "profile_id": "gemini_oauth"
}
```

Returns `202` with `{ "state": "pending" }` when the provider has not finished,
`429` with `{ "state": "slow_down" }` when the caller should back off, and `200`
with a `ready` payload after tokens are persisted.

### GET /auth/bindings/\{binding\_id}/status

Read binding-scoped auth status. The response includes the flattened binding
identity plus `profile_id`, `provider`, `auth_method`, public state,
expiration, refresh timestamp, account ID, and refresh-token presence.

<ParamField path="binding_id" type="string" required>
  Binding whose status should be read.
</ParamField>

<ParamField query="realm_id" type="string" required>
  Realm containing the binding.
</ParamField>

<ParamField query="profile_id" type="string | null">
  Optional explicit profile override for the binding.
</ParamField>

### POST /auth/bindings/\{binding\_id}/logout

Clear stored credentials for the binding and publish the auth lifecycle release.

<ParamField path="binding_id" type="string" required>
  Binding to log out.
</ParamField>

<ParamField query="realm_id" type="string" required>
  Realm containing the binding.
</ParamField>

<ParamField query="profile_id" type="string | null">
  Optional explicit profile override for the binding.
</ParamField>

### GET /realms

List configured realm summaries.

### GET /realms/\{id}

Read one realm connection set.

### POST /comms/send

Push a canonical comms command into a running session. Requires the `comms` feature.

<CodeGroup>
  ```json Request theme={null}
  {
    "session_id": "01936f8a-7b2c-7000-8000-000000000001",
    "kind": "peer_message",
    "to": "01936f8a-7b2c-7000-8000-000000000002",
    "body": "Please review the latest diff",
    "handling_mode": "queue",
    "source": "ci-pipeline"
  }
  ```

  ```json Response (202 Accepted) theme={null}
  {"queued": true}
  ```
</CodeGroup>

<ParamField body="session_id" type="string" required>
  Session ID to dispatch the comms command to.
</ParamField>

<ParamField body="kind" type="string" required>
  Command kind: `"input"`, `"peer_message"`, `"peer_lifecycle"`, `"peer_request"`, or `"peer_response"`.
</ParamField>

<ParamField body="to" type="string | null">
  Canonical peer ID to send to (required for `peer_message`, `peer_lifecycle`, `peer_request`, and `peer_response`).
</ParamField>

<ParamField body="body" type="string | null">
  Message body (required for `input` and `peer_message`).
</ParamField>

<ParamField body="handling_mode" type="string | null">
  Handling mode for `input`, `peer_message`, `peer_request`, and `peer_response`. Local callers must provide `"queue"` or `"steer"`.
</ParamField>

<Note>
  `peer_message` is the default collaboration primitive. `peer_lifecycle` is a one-way topology notification. `peer_request` is only a structured ask with correlated replies.
</Note>

<ParamField body="lifecycle_kind" type="string | null">
  Lifecycle event kind: `"mob.peer_added"`, `"mob.peer_retired"`, or `"mob.peer_unwired"` (required for `peer_lifecycle`).
</ParamField>

<ParamField body="intent" type="string | null">
  Request intent (required for `peer_request`, e.g. `"review"`, `"delegate"`).
</ParamField>

<ParamField body="params" type="object | null">
  Request parameters (optional for `peer_request`).
</ParamField>

<ParamField body="in_reply_to" type="string | null">
  ID of the request being responded to (required for `peer_response`).
</ParamField>

<ParamField body="status" type="string | null">
  Response status: `"accepted"`, `"completed"`, or `"failed"` (for `peer_response`).
</ParamField>

<ParamField body="result" type="object | null">
  Response result data (optional for `peer_response`).
</ParamField>

<ParamField body="source" type="string | null">
  Optional source label.
</ParamField>

<ParamField body="stream" type="string | null">
  Optional input stream mode: `"none"` or `"reserve_interaction"` (for `input` and `peer_request`).
</ParamField>

<ParamField body="allow_self_session" type="bool | null">
  Allow the session to send a message to itself (default: false).
</ParamField>

<Note>
  Unlike `POST /sessions/{id}/external-events` (which uses `RKAT_WEBHOOK_SECRET`
  header auth), `/comms/send` has **no authentication**. It is intended for
  trusted internal callers that already know the session ID.
</Note>

### GET /comms/peers

List discoverable peers for a session. Requires the `comms` feature.

<ParamField query="session_id" type="string" required>
  Session ID to query peers for.
</ParamField>

```json Response theme={null}
{
  "peers": [
    {
      "peer_id": "550e8400-e29b-41d4-a716-446655440001",
      "name": "reviewer",
      "address": {
        "transport": "tcp",
        "endpoint": "127.0.0.1:4201"
      },
      "source": "trusted",
      "sendable_kinds": ["peer_message", "peer_request", "peer_response"],
      "capabilities": {
        "version": 1,
        "extensions": {"review": true}
      },
      "meta": {
        "description": "Reviews pull requests",
        "labels": {"role": "reviewer"}
      }
    },
    {
      "peer_id": "550e8400-e29b-41d4-a716-446655440002",
      "name": "coordinator",
      "address": {
        "transport": "inproc",
        "endpoint": "coordinator"
      },
      "source": "inproc",
      "sendable_kinds": ["peer_message", "peer_request", "peer_response"],
      "capabilities": {"version": 1, "extensions": {}},
      "meta": {}
    }
  ]
}
```

`peer_id` is the canonical routing value accepted by `POST /comms/send`;
`name` is display-only and need not be unique. `address` is a typed
`{transport, endpoint}` object, while `source`, `sendable_kinds`,
`capabilities`, and `meta` describe discovery provenance and supported use.

## Error responses

All errors are returned as JSON with an HTTP status code:

```json theme={null}
{
  "error": "Human-readable error message",
  "code": "ERROR_CODE"
}
```

| HTTP Status | Code                      | When                                                               |
| ----------- | ------------------------- | ------------------------------------------------------------------ |
| 400         | `BAD_REQUEST`             | Invalid parameters, session ID mismatch, keep\_alive without comms |
| 403         | `HOOK_DENIED`             | Hook blocked the operation                                         |
| 404         | `SESSION_NOT_FOUND`       | Session does not exist                                             |
| 404         | `SKILL_NOT_FOUND`         | Requested skill does not exist                                     |
| 409         | `SESSION_BUSY`            | Turn already in progress                                           |
| 409         | `SESSION_NOT_RUNNING`     | Session not in running state                                       |
| 422         | `SKILL_RESOLUTION_FAILED` | Skill resolution error                                             |
| 429         | `BUDGET_EXHAUSTED`        | Resource limits reached                                            |
| 500         | `AGENT_ERROR`             | LLM provider error, tool dispatch failure, agent loop error        |
| 500         | `INTERNAL_ERROR`          | Store initialization failure, unexpected server error              |
| 501         | `CAPABILITY_UNAVAILABLE`  | Required capability not compiled in or disabled                    |
| 502         | `PROVIDER_ERROR`          | LLM provider issue (missing key, auth failure)                     |

For a session that was durably created but whose first turn failed, the server
returns `SESSION_CREATED_WITH_TURN_FAILURE` with resumable session identity in
`details`. A genuinely unknown provider failure now runs the bounded retry
policy; when that policy is exhausted, `details.error.kind` is
`retry_exhausted` rather than `llm_failure`. Clients that branch on this nested
kind should accept the new value. Known non-retryable LLM failures can still use
`llm_failure`.

<CodeGroup>
  ```json Session not found theme={null}
  {
    "error": "Session not found: 01936f8a-7b2c-7000-8000-000000000099",
    "code": "SESSION_NOT_FOUND"
  }
  ```

  ```json Bad request theme={null}
  {
    "error": "Session ID mismatch: path=abc body=def",
    "code": "BAD_REQUEST"
  }
  ```
</CodeGroup>

## Notes

<Note>
  Keep-alive mode requires `keep_alive: true` and a `comms_name`. If `keep_alive` is
  requested but the binary was not compiled with comms support, the server
  returns a `BAD_REQUEST` error.
</Note>

<Note>
  `POST /sessions/{id}/external-events` queues a runtime-backed external event. It is a queue-only admission path, not a second direct execution loop.
</Note>

<Note>
  `hooks_override` allows per-request hook overrides including adding extra hook
  entries and disabling specific hooks by ID. See [Hooks](/guides/hooks) for the
  `HookRunOverrides` schema.
</Note>
