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

# WebSocket

> A bidirectional session for live chat UIs — streaming replies with gap-free recovery.

The WebSocket session is the richest transport for chat UIs: send message
frames, receive the reply as a live token stream, and recover any missed
events after a reconnect. It complements the [REST and SSE
transports](/api/messaging/send-messages) — same conversation model, same
threads.

***

## Connect

```
wss://api-v3.aui.io/apollo-api/messaging/v1/session
```

Authenticate on the upgrade request with your Bearer token:

```
Authorization: Bearer <access_token>
```

<Warning>
  Invalid or expired credentials close the connection with code `1008`
  (policy violation). Internal errors close with `1011`.
</Warning>

### Query parameters

<ParamField query="include_trace" type="boolean" default="true">
  When true, the final `message` envelope carries `data.trace_info` — the
  reasoning [trace](/api/messaging/traces).
</ParamField>

<ParamField query="events" type="string">
  Omit for the minimal stream (token deltas + completion). `all` or `verbose`
  opts into the full workflow/lifecycle event set.
</ParamField>

<ParamField query="resume_after" type="integer">
  With `task_id`, replays buffered envelopes with `seq` greater than this value
  on connect — gap recovery after a reconnect.
</ParamField>

<ParamField query="task_id" type="string">
  Thread id to replay from on connect (pairs with `resume_after`).
</ParamField>

***

## Frames you send

### `message` — submit a turn

```json theme={"dark"}
{
  "type": "message",
  "agent_id": "6a344d186d3160971ec62eb6",
  "thread_id": null,
  "user_id": "my-user-123",
  "text": "I am looking for a built-in microwave",
  "context": { "url": "https://example.com/products" },
  "agent_variables": {}
}
```

<ParamField body="type" type="string" required>
  Must be `"message"`.
</ParamField>

<ParamField body="agent_id" type="string" required>
  The target agent — use the `agent_id` returned by the
  [token exchange](/api/authentication).
</ParamField>

<ParamField body="thread_id" type="string">
  Omit to auto-create a thread; its id is announced back as the first
  envelope. Pass it to continue an existing thread.
</ParamField>

<ParamField body="user_id" type="string">
  End-user reference for an auto-created thread.
</ParamField>

<ParamField body="text" type="string" required>
  The message content.
</ParamField>

### `resume` — replay after a reconnect

```json theme={"dark"}
{ "type": "resume", "resume_after": 41 }
```

Replays buffered envelopes with `seq > resume_after` for the active thread.

***

## Envelopes you receive

Every server frame is a sequenced envelope:

```json theme={"dark"}
{ "seq": 42, "type": "event", "data": { ... } }
```

| `type`    | Meaning                                                | `data`                                                                                       |
| --------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
| `thread`  | The resolved thread id, once per thread per connection | `{ "thread_id": "..." }`                                                                     |
| `event`   | A streaming event while the agent works                | Token deltas carry `data.text` under event name `thread-message-text-content-updated`        |
| `message` | Turn complete — the full agent reply                   | The same `message` object as REST, plus `trace_info` when `include_trace=true`               |
| `error`   | A recoverable error; the socket stays open             | `{ "error": "...", "code": 402 }` for session errors, or the turn error's message and status |

Track the highest `seq` you've processed — that's your resume cursor.
Locally-generated errors use `seq: -1` and are not resumable.

***

## Code example

```javascript Node.js theme={"dark"}
const WebSocket = require("ws");

let lastSeq = -1;

const ws = new WebSocket(
  "wss://api-v3.aui.io/apollo-api/messaging/v1/session",
  { headers: { Authorization: `Bearer ${accessToken}` } }
);

ws.on("open", () => {
  ws.send(JSON.stringify({
    type: "message",
    agent_id: agentId,        // from the token exchange
    user_id: "my-user-123",
    text: "I am looking for a built-in microwave",
  }));
});

ws.on("message", (raw) => {
  const envelope = JSON.parse(raw);
  if (envelope.seq > 0) lastSeq = envelope.seq;

  switch (envelope.type) {
    case "thread":
      console.log("thread:", envelope.data.thread_id);
      break;
    case "event":
      if (envelope.data?.data?.text) {
        process.stdout.write(envelope.data.data.text); // token delta
      }
      break;
    case "message":
      console.log("\ncomplete:", envelope.data.text);
      break;
    case "error":
      console.error("error:", envelope.data);
      break;
  }
});

ws.on("close", (code) => {
  if (code !== 1000) {
    // reconnect, then recover the gap:
    // ?task_id=<thread_id>&resume_after=<lastSeq>
  }
});
```

***

## Best practices

* **Reconnect with resume** — on an abnormal close, reconnect with
  `task_id` + `resume_after` (or send a `resume` frame) instead of restarting
  the turn; you get exactly the envelopes you missed.
* **Track `seq` continuously** — a gap in `seq` means you missed frames; resume
  from your last processed value.
* **Exponential backoff** — back off between reconnection attempts.
* **Keep the thread id** — persist `thread_id` across reconnects to stay in the
  same conversation.

The full machine-readable contract is published as an AsyncAPI document at
[`/asyncapi.yaml`](https://api-v3.aui.io/apollo-api/asyncapi.yaml).
