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

# Flutter headless client

> Build your own Flutter chat interface on the Osvi AI chat client

`OsviChatClient` is the chat without an interface: it opens the session, keeps the connection alive, and gives you the transcript as state. The SDK's own widgets are built on it, so anything they do, your interface can do too.

```dart theme={null}
final chat = await OsviChatClient.create(agentId: 'agent_yourAgentId');
await chat.start();

ValueListenableBuilder<ChatState>(
  valueListenable: chat,
  builder: (context, state, _) => ListView(
    children: [
      for (final item in state.items)
        if (item is Message) Text('${item.role}: ${item.content}'),
    ],
  ),
);

await chat.send('Do you have rooms on Friday?');
```

Creating a client opens no connection. `start()`, or the first `send()`, does. `OsviChatView` calls `start()` itself, so with the SDK's widgets you only call it to connect before the visitor opens the chat, for example so the launcher's unread badge counts replies to a returning visitor's conversation.

## Creating a client

`OsviChatClient.create(agentId:, baseUrl:, auth:)` is the one to use in an app. It stores the visitor ID and current session with `shared_preferences`, and reconnects when the app returns to the foreground.

`baseUrl` defaults to `https://api.osvi.ai`. `auth` defaults to `ChatAuth.anonymous()`; for signed-in customers see [Logged-in users](https://docs.osvi.ai/flutter/server-mode.md).

For tests or custom environments, construct the client directly with `OsviChatClient(ChatClientOptions(…))`. Its hooks are `httpClient` (an `http.Client`), `socketFactory`, `store` (a `KeyValueStore`, such as `MemoryStore`), `page` (`PageSignals`, which tell the client when the app is visible and online), and `random`. Without a `store` the client keeps nothing between runs.

## State

The client is a `ValueListenable<ChatState>`, so it works with `ValueListenableBuilder`, `addListener` and `client.value`. `client.stream` emits the same states as a broadcast `Stream`.

| Field | What it holds |
| - | - |
| `status` | `idle`, `connecting`, `open`, `recovering`, `offline` or `closed` |
| `mode` | `ai`, or `human` once a team member has taken over |
| `awaitingReply` | The agent is working on a reply. Show a typing indicator |
| `supportTyping` | A team member is typing |
| `items` | The transcript: `Message`, `InteractionItem`, `Divider` (a conversation ended) and `WelcomeItem` |
| `activeInteraction` | The card waiting for an answer, if any |
| `interactionPending` | An answer to that card is on its way |
| `agent` | The agent's ID and name, once known |
| `error` | The current `ChatError`, or null |
| `extras` | Dashboard settings for the chat: appearance, pre-chat form, WhatsApp hand-off |
| `visitor` | The pre-chat details, once submitted on this device |

A `Message` has an `id`, a `role` (`user`, `assistant` or `human` for a team member), `content` in markdown, `createdAt`, a `status` of `sending`, `sent` or `failed`, and any `reactions`.

## Methods

| Method | What it does |
| - | - |
| `start()` | Connects. In anonymous mode it loads the agent and resumes the visitor's conversation; in server mode it calls `getToken` |
| `send(text)` | Sends a message. Resolves to a `SendOutcome` |
| `respond(interactionId, response)` | Answers a card. `response` is `InteractionResponse.selection(optionId)`, `.text(text)`, `.date('2026-11-14')` or `.dateRange(start, end)`. Resolves to `true` if it was sent |
| `retry(messageId)` | Sends a failed message again, with the same idempotency key so it is never delivered twice. Resolves to `true` if it was resent |
| `newSession(keepHistory: true)` | Ends the conversation and starts a new one. Pass `keepHistory: false` to clear the transcript |
| `submitPrechat(VisitorInfo(name: …, phone: …))` | Saves the pre-chat form. Resolves to a `PrechatOutcome` |
| `whatsappHandoff()` | Gets the WhatsApp link to continue the conversation there. Resolves to a `HandoffOutcome` with the `url` |
| `dispose()` | Closes the connection and stops the client for good |

`send` resolves to `ok: true` with the `messageId` once the message is queued, or `ok: false` with a `reason`: `empty`, `destroyed`, `awaiting_interaction` (answer the card first) or `unavailable`. `ok: true` does not mean the server has it: the message's `status` turns `sent` when the server confirms it. The client resends unconfirmed messages after a reconnect, and retries busy and rate-limited sends by itself.

## Errors

`state.error` has a stable `code`, a `message`, a `retryable` flag and, for waits, `retryAfterSeconds`. The codes are constants on `ChatErrorCode` and are the same as the website SDK's; each one is explained in [Error codes](https://docs.osvi.ai/website-chat/error-codes.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.