> ## 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.

# Migration Guide (v2 → v3)

> The exact before/after for every call and response when upgrading @aui.io/aui-client from v2 to v3.

The v3 SDK (`@aui.io/aui-client@^3`) is a ground-up rewrite of v2 (`^1.2.x`):
tasks became threads, the single client split in two, and the agent now rides
your key. This page is the exact before/after for every call and response —
10 methods mapped, 2 removed, 1 changed meaning.

## What changed at a glance

Two substitutions do most of the work: every `task_id` becomes a `thread_id`,
and every `client.controllerApi.X` moves onto one of two new clients.

| Area        | v2                                        | v3                                                                        |
| ----------- | ----------------------------------------- | ------------------------------------------------------------------------- |
| Core model  | One **Task** holds the whole conversation | **Thread** → Interaction → Message                                        |
| Client      | One `ApolloClient`                        | `ApolloMessagingClient` + `ApolloManagementClient`                        |
| Surface     | Everything under `controllerApi.*`        | `messaging`, `channels`, `threads`, `projects`, `agents`, `agentVersions` |
| Auth        | One `networkApiKey`                       | **Publishable key** (browser) + **org API key** (server)                  |
| Agent       | Passed per call                           | Encoded in the key (REST); explicit on WebSocket                          |
| Environment | `azure`/`gcp` × `v2`/`v3` URL matrix      | Production default; optional `baseUrl`                                    |
| Typing      | Loose — `as unknown as T`                 | Fully typed `Apollo.*` responses                                          |

## Connecting & authentication

The single client was split by **who may hold the credential**. The messaging
client is browser-safe; the management client is server-side only.

```ts v2 — one client, one key theme={"dark"}
import { ApolloClient } from '@aui.io/aui-client';

const client = new ApolloClient({
  networkApiKey: 'API_KEY_XXXX',
  environment: {
    base:  'https://api-v3.aui.io/ia-controller',
    wsUrl: 'wss://api-v3.aui.io',
  },
  timeoutInSeconds: 60,
  maxRetries: 3,
});
```

```ts v3 — two scoped clients theme={"dark"}
import {
  ApolloMessagingClient,
  ApolloManagementClient,
} from '@aui.io/aui-client';

// Browser-safe · publishable key
const messaging = new ApolloMessagingClient({
  publishableKey: 'pk_network_XXXX',
});

// Server only · organization API key
const management = new ApolloManagementClient({
  organizationApiKey: 'org_XXXX',
});
```

<Info>
  **The publishable key authenticates for you.** It is exchanged for a
  short-lived bearer token automatically — you no longer set
  `x-network-api-key` headers anywhere, including on the WebSocket. The
  organization key is sent as `x-organization-api-key` and must never reach a
  browser.
</Info>

### What went away

The `azure`/`gcp` × `v2`/`v3` URL matrix is gone — both clients default to
production. Pass `baseUrl` only to target a different host. Per-call
`timeoutInSeconds` and retries moved from the constructor into each method's
`requestOptions` (last argument); set a generous timeout on the aggregating
list endpoints, which can be slow.

## Method map

Below, `messaging` is an `ApolloMessagingClient` and `management` is an
`ApolloManagementClient`. Messaging methods hang off the `.messaging`
sub-resource.

| v2 call                          | v3 call                                                    | Client     |
| -------------------------------- | ---------------------------------------------------------- | ---------- |
| `createTask()`                   | **removed** — threads auto-create                          | —          |
| `listUserTasks()`                | `threads.listThreads()`                                    | Management |
| `getTaskMessages()`              | `threads.getThreadMessages()` · `messaging.listMessages()` | Both       |
| `sendMessage()`                  | `messaging.sendMessage()`                                  | Messaging  |
| `startTextConversation()`        | `channels.initiateThread()`                                | Messaging  |
| `getAgentContext()`              | **meaning changed** → [see below](#get-agent-context)      | —          |
| `getDirectFollowupSuggestions()` | `messaging.generateFollowupSuggestions()`                  | Messaging  |
| `getTraceInfo()`                 | `threads.getThreadTrace()` · `getInteractionTrace()`       | Both       |
| `getProductMetadata()`           | **removed** — no replacement                               | —          |
| `apolloWsSession.connect()`      | `messaging.connect()`                                      | Messaging  |

## Method by method

### Create Task — `removed`

There is no `createTask`. A thread is created the first time you talk to the
agent without a `thread_id` — capture it from the response.

```ts v2 theme={"dark"}
const task = await client.controllerApi.createTask({
  user_id,
  task_origin_type: 'web-widget',
});
const taskId = task.id;
```

```ts v3 theme={"dark"}
const res = await messaging.sendMessage({
  user_id,
  text: 'Hello',
});
const threadId = res.thread_id;
```

### List User Tasks — `threads · management`

The user filter moved into `filters`, and pagination is now **cursor-based**
(`page[size]` / `page[after]`) instead of `page`/`size`.

```ts v2 theme={"dark"}
const r = await client.controllerApi.listUserTasks({
  user_id, page: 1, size: 10,
});
r.tasks.forEach(t => t.id);
// { tasks, total, page, size }
```

```ts v3 theme={"dark"}
const page = await management.threads.listThreads(
  {
    filters: { user_id },
    'page[size]': 10,
    sort_by: 'created_at',
    sort_order: 'desc',
  },
  { timeoutInSeconds: 120 },
);
page.results.forEach(t => t.id);
// { results, meta, links }
```

### Get Task Messages — `both clients`

Same data, two entry points depending on which key you hold. Both return
`Apollo.Message[]`.

```ts v2 theme={"dark"}
const messages =
  await client.controllerApi.getTaskMessages(taskId);
```

```ts v3 theme={"dark"}
// server-side (org key)
await management.threads.getThreadMessages(threadId);

// client-side (publishable key)
await messaging.listMessages(threadId);
```

### Send Message (REST) — `signature changed`

`user_id` is **now required**. `task_id` → optional `thread_id`.
`is_external_api` and `agent_id` are gone (the agent rides the key). The
reply is now nested under `.message`.

```ts v2 theme={"dark"}
const res = await client.controllerApi.sendMessage({
  task_id: taskId,
  text: 'Hello',
  is_external_api: true,
  agent_variables: { foo: 'bar' },
});
console.log(res.text);
// { id, text, sender, cards, followupSuggestions }
```

```ts v3 theme={"dark"}
const res = await messaging.sendMessage({
  user_id: 'user-123',        // REQUIRED
  text: 'Hello',
  thread_id: threadId,        // optional -> omit to create
  image_url: 'https://…',     // optional (vision)
  agent_variables: { static: { name: 'Ada' } },
});
console.log(res.message.text);   // nested now
const threadId = res.thread_id;
// { thread_id, message }
```

### Start Text Conversation — `channels · messaging`

`channel` is now positional, fields are snake\_case, and `message` splits by
channel: SMS uses `text`; WhatsApp uses templates via `agent_display_name`.

```ts v2 theme={"dark"}
const res =
  await client.controllerApi.startTextConversation({
    phoneNumber: '+14155551234',
    channel: 'sms',
    message: 'Hello!',
  });
// { task_id | taskId | id }
```

```ts v3 theme={"dark"}
const res = await messaging.channels.initiateThread(
  'sms',                           // positional
  {
    phone_number: '+14155551234',  // snake_case
    text: 'Hello!',                // opener (SMS)
  },
);
const threadId = res.thread_id;
// { thread_id, message_sid }
```

### Get Agent Context — `meaning changed`

<Warning>
  **Not a drop-in rename.** v2 returned the agent's *content configuration*
  for a task (`params`, `entities`, `static_context`). v3 `getContext()`
  returns what your *key resolves to* — it is an auth-scope helper. The old
  configuration now lives in the agent version, managed via
  `management.agentVersions.*`.
</Warning>

```ts v2 — agent config for a task theme={"dark"}
const ctx = await client.controllerApi.getAgentContext({
  task_id: taskId,
  query: 'test',
});
// { title, params, entities, static_context }
```

```ts v3 — key scope (different!) theme={"dark"}
const scope = await messaging.getContext();
// { agentId?, organizationId?, keyType }
```

### Follow-up Suggestions — `signature changed`

No longer keyed by a task id — you pass a `context` object. Returns an
object, not a bare `string[]`. (Threads and messages also carry
`followup_suggestions` inline, so you often need no separate call.)

```ts v2 theme={"dark"}
const s =
  await client.controllerApi
    .getDirectFollowupSuggestions(taskId);
// string[]
```

```ts v3 theme={"dark"}
const res =
  await messaging.generateFollowupSuggestions({
    context: { /* your context */ },
    created_by: 'optional',
  });
const s = res.suggestions ?? [];
// { suggestions?, metadata_id? }
```

### Get Trace Info — `both clients`

Split into a whole-thread trace and a single-interaction trace.
`interactionId` is the successor to the old per-message id.

```ts v2 theme={"dark"}
const trace =
  await client.controllerApi.getTraceInfo(
    taskId, messageId,
  );
```

```ts v3 theme={"dark"}
await management.threads.getThreadTrace(threadId);
await management.threads.getInteractionTrace(interactionId);

// also on the messaging client:
await messaging.threadTrace(threadId);
await messaging.interactionTrace(interactionId);
```

### Get Product Metadata — `removed`

`getProductMetadata({ link })` has no equivalent in the v3 public surface.
Remove these calls.

## WebSocket

The socket was rewritten: no manual auth headers, a typed received envelope,
and a renamed send method.

|          | v2                                     | v3                                              |
| -------- | -------------------------------------- | ----------------------------------------------- |
| Open     | `apolloWsSession.connect({ headers })` | `messaging.connect()` — no headers              |
| Ready    | `on('open')`                           | `await socket.waitForOpen()`                    |
| Send     | `sendUserMessage({ task_id, text })`   | `sendMessage({ type: 'message', agent_id, … })` |
| Received | channel-shaped object                  | `{ type, data, seq }`                           |

```ts v2 theme={"dark"}
const socket = await client.apolloWsSession.connect({
  debug: true,
  reconnectAttempts: 3,
  headers: { 'x-network-api-key': apiKey },
});
socket.on('open', () => {});
socket.on('message', (msg) => {
  // streaming: msg.channel?.eventName === '…updated'
  //            -> msg.data.text
  // final:     msg.id && msg.text && msg.sender
  // error:     msg.statusCode -> msg.description
});
socket.sendUserMessage({
  task_id: taskId, text: 'Hello',
});
socket.close();
```

```ts v3 theme={"dark"}
import type { SessionSocket } from '@aui.io/aui-client';

const socket = await messaging.connect(); // no headers
socket.on('message', (env) => {           // { type, data, seq }
  switch (env.type) {
    case 'thread':  env.data.thread_id;      break;
    case 'event':   env.data.data.text;      break; // token
    case 'message': /* terminal: full Message */ break;
    case 'error':   env.data.error;          break;
  }
});
await socket.waitForOpen();

socket.sendMessage({
  type: 'message',
  agent_id: 'agent-123',   // STILL required on WS
  text: 'Hello',
  thread_id: threadId,     // optional
});
socket.close();
```

<Warning>
  **WebSocket gotchas.** `agent_id` is still required on the socket even
  though REST `sendMessage` dropped it. `on()` *replaces* a handler —
  register each event once. The submit method was renamed
  `sendSubmitMessage` → `sendMessage` in v3.2.2; if you span patch versions,
  dispatch on whichever exists.
</Warning>

## Response shapes

Responses are fully typed now — delete the v2-era `as unknown as T` casts.
The `Message` object below is what you read from `sendMessage`,
`listMessages`, `getThreadMessages`, and the WS terminal frame.

```ts theme={"dark"}
interface Message {
  id: string;                 // interaction id
  created_at?: string;        // ISO-8601
  text?: string;
  sender?:   { id?: string; type?: string };  // was sender.name in v2
  receiver?: { id?: string; type?: string };
  cards?: MessageCard[];      // { name, category, rendered_jsx, is_recommended, index }
  followup_suggestions?: string[];   // snake_case
  input_tokens?: number;
  output_tokens?: number;
}

interface SendMessageResponse    { thread_id: string; message: Message; }
interface InitiateThreadResponse { thread_id: string; message_sid: string; }
interface PageThreadListItem     { results: ThreadListItem[]; meta; links; }
interface ThreadListItem         { id: string; title?: string; created_at?: string; }
interface GenerateFollowupSuggestionsResponse { suggestions?: string[]; metadata_id?: string; }
```

<Info>
  **New in v3, no v2 equivalent:** the Management APIs (`projects`, `agents`,
  `agentVersions`), SSE streaming via `messaging.streamMessage()`,
  `messaging.rerun()`, `messaging.getWelcomeMessage()`, and
  `threads.updateThread()`.
</Info>

## Migration checklist

Work top to bottom.

* [ ] Bump the dependency to `@aui.io/aui-client@^3`.
* [ ] Replace `new ApolloClient()` with `ApolloMessagingClient` (browser) and/or `ApolloManagementClient` (server).
* [ ] Swap the single `networkApiKey` for a publishable key and/or org API key — never ship the org key to a browser.
* [ ] Delete the environment/URL matrix; rely on the production default (or set `baseUrl` to override).
* [ ] Rename every `task_id` → `thread_id`; stop calling `createTask` and capture `thread_id` from the first response.
* [ ] Update `sendMessage`: add required `user_id`, drop `is_external_api`/`agent_id`, read the reply from `res.message`.
* [ ] Move `startTextConversation` → `channels.initiateThread(channel, { phone_number, text? })`.
* [ ] Re-point `listUserTasks` → `threads.listThreads({ filters: { user_id } })` and read `results` (cursor pagination).
* [ ] Replace `getTraceInfo` with `getThreadTrace` / `getInteractionTrace`.
* [ ] Audit `getAgentContext` — `getContext()` is not the same thing.
* [ ] Remove `getProductMetadata` calls (no replacement).
* [ ] Rewrite the WebSocket: `messaging.connect()`, no auth headers, branch on `env.type`, send with `sendMessage({ type: 'message', agent_id })`.
* [ ] Move per-call `timeoutInSeconds` into `requestOptions`; set a generous timeout on list endpoints.
* [ ] Drop `as unknown as` casts — responses are typed.
