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

# Build voice interfaces with the React SDK

> Use @xuna-ai/react to build custom voice agent UIs with full control over session lifecycle, audio, client tools, and conversation state.

The `@xuna-ai/react` package gives you hooks and providers to build completely custom voice interfaces in React. Use it when you need control the widget doesn't provide — custom UI, client-side tools, dynamic overrides, or fine-grained state management.

## Installation

<Tabs>
  <Tab title="npm">
    ```bash theme={null}
    npm install @xuna-ai/react
    ```
  </Tab>

  <Tab title="yarn">
    ```bash theme={null}
    yarn add @xuna-ai/react
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash theme={null}
    pnpm install @xuna-ai/react
    ```
  </Tab>
</Tabs>

`@xuna-ai/react` re-exports everything from `@xuna-ai/client`, so you only need one dependency.

## Minimal example

Wrap your app in `ConversationProvider`, then use hooks inside it to start and stop sessions.

```tsx theme={null}
import {
  ConversationProvider,
  useConversationControls,
  useConversationStatus,
} from '@xuna-ai/react';

function App() {
  return (
    <ConversationProvider>
      <Agent />
    </ConversationProvider>
  );
}

function Agent() {
  const { startSession, endSession } = useConversationControls();
  const { status } = useConversationStatus();

  if (status === 'connected') {
    return <button onClick={endSession}>End conversation</button>;
  }

  return (
    <button
      onClick={() =>
        startSession({ agentId: 'agent_7101k5zvyjhmfg983brhmhkd98n6' })
      }
    >
      Start conversation
    </button>
  );
}
```

## ConversationProvider

Place `ConversationProvider` at the root of any component tree that needs access to conversation state. It accepts the following props:

| Prop             | Type                       | Description                                                                   |
| ---------------- | -------------------------- | ----------------------------------------------------------------------------- |
| `onConnect`      | `() => void`               | Called when the session connects.                                             |
| `onDisconnect`   | `() => void`               | Called when the session ends.                                                 |
| `onError`        | `(error: Error) => void`   | Called on session errors.                                                     |
| `clientTools`    | `Record<string, Function>` | Tool handlers the agent can invoke on the client.                             |
| `serverLocation` | `string`                   | Region for the conversation server (see [Server location](#server-location)). |
| `isMuted`        | `boolean`                  | Controlled mute state for the microphone.                                     |
| `onMutedChange`  | `(muted: boolean) => void` | Called when mute state changes.                                               |

## Hooks

Use granular hooks to avoid unnecessary re-renders. Each hook subscribes only to the state it needs.

### useConversationControls

Session lifecycle and messaging actions.

```tsx theme={null}
const {
  startSession,
  endSession,
  sendUserMessage,
  sendContextualUpdate,
  setVolume,
  sendFeedback,
} = useConversationControls();
```

| Function                     | Description                                                   |
| ---------------------------- | ------------------------------------------------------------- |
| `startSession(options)`      | Start a new conversation session.                             |
| `endSession()`               | End the current session.                                      |
| `sendUserMessage(text)`      | Send a text message as the user.                              |
| `sendContextualUpdate(text)` | Inject background context without creating a visible message. |
| `setVolume(volume)`          | Set agent output volume (0–1).                                |
| `sendFeedback(feedback)`     | Submit thumbs-up / thumbs-down feedback for the session.      |

### useConversationStatus

Current connection state.

```tsx theme={null}
const { status, message } = useConversationStatus();
// status: 'disconnected' | 'connecting' | 'connected'
```

### useConversationInput

Microphone mute control.

```tsx theme={null}
const { isMuted, setMuted } = useConversationInput();
```

### useConversationMode

Whether the agent is speaking or listening.

```tsx theme={null}
const { mode, isSpeaking, isListening } = useConversationMode();
```

### useConversationFeedback

Submit session feedback.

```tsx theme={null}
const { canSendFeedback, sendFeedback } = useConversationFeedback();
```

### useConversationClientTool

Register a client tool from inside a component. The tool name must match what you configured for the agent in the dashboard.

```tsx theme={null}
useConversationClientTool('showProductCard', (params: { productId: string }) => {
  // render a product card in your UI
  setSelectedProduct(params.productId);
  return 'Product card displayed';
});
```

### useConversation

Combines all hooks in one. Convenient for simple components, but causes more re-renders than using granular hooks.

```tsx theme={null}
const conversation = useConversation();
```

<Tip>
  Prefer the granular hooks (`useConversationControls`, `useConversationStatus`, etc.) in performance-sensitive components. Use `useConversation` only in small or infrequent-rendering components.
</Tip>

## startSession options

Pass options to `startSession` to control how the session connects.

| Option              | Description                                                     |
| ------------------- | --------------------------------------------------------------- |
| `agentId`           | Agent ID for public agents.                                     |
| `signedUrl`         | Signed WebSocket URL for private agents (WebSocket auth).       |
| `conversationToken` | Token for private agents (WebRTC auth).                         |
| `userId`            | Optional end-user identifier for analytics and personalization. |

## Authenticating private agents

If your agent is private, generate a credential on your server and pass it to `startSession`. Choose WebSocket (signed URL) or WebRTC (conversation token) based on your stack.

<Tabs>
  <Tab title="WebSocket — signed URL">
    **Server**

    ```js theme={null}
    app.get('/signed-url', yourAuthMiddleware, async (req, res) => {
      const response = await fetch(
        `https://api.xuna.ai/v1/convai/conversation/get-signed-url?agent_id=${process.env.AGENT_ID}`,
        { headers: { 'x-api-key': process.env.XUNA_AI_API_KEY } }
      );
      const body = await response.json();
      res.send(body.signed_url);
    });
    ```

    **Client**

    ```tsx theme={null}
    const { startSession } = useConversationControls();

    async function connect() {
      const response = await fetch('/signed-url', yourAuthHeaders);
      const signedUrl = await response.text();
      await startSession({ signedUrl });
    }
    ```
  </Tab>

  <Tab title="WebRTC — conversation token">
    **Server**

    ```js theme={null}
    app.get('/conversation-token', yourAuthMiddleware, async (req, res) => {
      const response = await fetch(
        `https://api.xuna.ai/v1/convai/conversation/token?agent_id=${process.env.AGENT_ID}`,
        { headers: { 'x-api-key': process.env.XUNA_AI_API_KEY } }
      );
      const body = await response.json();
      res.send(body.token);
    });
    ```

    **Client**

    ```tsx theme={null}
    const { startSession } = useConversationControls();

    async function connect() {
      const response = await fetch('/conversation-token', yourAuthHeaders);
      const conversationToken = await response.text();
      await startSession({ conversationToken });
    }
    ```
  </Tab>
</Tabs>

<Warning>
  Never include your `XUNA_AI_API_KEY` in client-side code. Always call the XUNA AI API from your server.
</Warning>

## Client tools

Client tools let the agent call functions in your React app during a conversation — for example, to display information, submit a form, or navigate the UI. Define them on `ConversationProvider` or register them with `useConversationClientTool`.

```tsx theme={null}
<ConversationProvider
  clientTools={{
    displayMessage: (parameters: { text: string }) => {
      alert(parameters.text);
      return 'Message displayed';
    },
  }}
>
  <Agent />
</ConversationProvider>
```

The return value is sent back to the agent as the tool result.

## Overrides

Pass `overrides` to `startSession` to customize agent behavior for a specific session without changing the agent's dashboard configuration.

```tsx theme={null}
await startSession({
  agentId: 'agent_7101k5zvyjhmfg983brhmhkd98n6',
  overrides: {
    agent: {
      prompt: { prompt: 'You are a helpful assistant for Acme Corp.' },
      firstMessage: 'Hi! How can I help you today?',
      language: 'en',
    },
    tts: {
      voiceId: 'your_custom_voice_id',
    },
  },
});
```

## Server location

By default, conversation servers are located in the US. Set `serverLocation` on `ConversationProvider` to use a different region.

| Value            | Region                   |
| ---------------- | ------------------------ |
| `"us"`           | United States (default)  |
| `"eu-residency"` | European Union           |
| `"in-residency"` | India                    |
| `"global"`       | Closest available region |

```tsx theme={null}
<ConversationProvider serverLocation="eu-residency">
  <Agent />
</ConversationProvider>
```

## Next steps

* To deploy without a custom UI, use the [widget](/deploy/widget) instead.
* For mobile apps, see the [Mobile SDKs](/deploy/mobile-sdks) page.
* To build a fully custom integration outside React, use the [WebSocket API](/deploy/websocket-api).
