> For clean Markdown of any page, append `.md` to the page URL.
> For a complete documentation index, see https://docs.sarvam.ai/llms.txt.
> For full documentation content in one file, see https://docs.sarvam.ai/llms-full.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.sarvam.ai/_mcp/server.

# React Native

For real-time voice on iOS and Android, the TypeScript SDK (`sarvam-conv-ai-sdk/react-native`) uses the same `ConversationAgent` client as [Web](/conversations/deploy/sdks/web). React Native has no built-in browser audio API, so you must pass your own `AsyncAudioInterface` for microphone capture and playback.

## Install

```bash
npm install sarvam-conv-ai-sdk
```

```typescript
import { ConversationAgent } from "sarvam-conv-ai-sdk/react-native";
```

## Start a voice session

```typescript
import {
  ConversationAgent,
  InteractionType,
} from "sarvam-conv-ai-sdk/react-native";

const audioInterface = new MyRNAudioInterface();

const agent = new ConversationAgent({
  apiKey: "your_api_key",
  config: {
    org_id: "your_org_id",
    workspace_id: "your_workspace_id",
    app_id: "your_app_id",
    user_identifier: "user123",
    user_identifier_type: "custom",
    interaction_type: InteractionType.CALL,
    input_sample_rate: 16000,
    output_sample_rate: 16000,
  },
  audioInterface,
  platform: "react-native",
  transcriptCallback: async (msg) => {
    console.log(`${msg.role}: ${msg.content}`);
  },
});

await agent.start();
const connected = await agent.waitForConnect(10);
if (!connected) {
  // Show an error or retry
}
```

Set `platform: "react-native"` and pass an `audioInterface` that implements `AsyncAudioInterface` (see [Custom audio interface](#custom-audio-interface) below).

## InteractionConfig

| Field                   | Required | Description                                                                                                                                                    |
| ----------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user_identifier_type`  | Yes      | `'custom'`, `'email'`, `'phone_number'`, or `'unknown'`                                                                                                        |
| `user_identifier`       | Yes      | The identifier value; also what you search by in [Log Analyser](/conversations/monitor/agent-analytics/log-analyser)                                           |
| `org_id`                | Yes      | Your organization ID                                                                                                                                           |
| `workspace_id`          | Yes      | Your workspace ID                                                                                                                                              |
| `app_id`                | Yes      | The agent to connect to                                                                                                                                        |
| `interaction_type`      | Yes      | `InteractionType.CALL` for a voice session                                                                                                                     |
| `input_sample_rate`     | Yes      | `8000` or `16000` Hz                                                                                                                                           |
| `output_sample_rate`    | Yes      | `16000` or `22050` Hz                                                                                                                                          |
| `version`               | No       | Pins a specific committed agent version. If omitted, the SDK uses the latest committed version, and the connection fails if the agent has no committed version |
| `agent_variables`       | No       | Seed [agent variables](/conversations/build/variables-personalization) at session start                                                                        |
| `initial_language_name` | No       | Starting language; must be one of the agent's allowed languages                                                                                                |
| `initial_state_name`    | No       | Starting state, if the agent uses [states](/conversations/build/states-conversation-flow)                                                                      |
| `initial_bot_message`   | No       | First message from the agent                                                                                                                                   |

## Callbacks

Pass any of these to `ConversationAgent` to react to what happens during the call:

| Callback             | Fires with                                                        | Use it for                                                                           |
| -------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `transcriptCallback` | transcript message (`role`: `Role.USER` or `Role.BOT`, `content`) | A live transcript of both sides of the call                                          |
| `audioCallback`      | audio chunk                                                       | Raw agent audio, if you're handling playback yourself                                |
| `audioLevelCallback` | output level                                                      | Real-time level for a speaking indicator                                             |
| `eventCallback`      | session event                                                     | Session events such as connect, barge-in, and end                                    |
| `stateCallback`      | `AgentState`                                                      | Drive UI from `IDLE`, `CONNECTING`, `CONNECTED`, `LISTENING`, `SPEAKING`, or `ERROR` |
| `startCallback`      | —                                                                 | Session start                                                                        |
| `endCallback`        | —                                                                 | Session end and cleanup                                                              |

## Custom audio interface

Implement `AsyncAudioInterface` and feed 16-bit PCM mono at the configured sample rate:

```typescript
import {
  AsyncAudioInterface,
  AudioData,
} from "sarvam-conv-ai-sdk/react-native";

class MyRNAudioInterface implements AsyncAudioInterface {
  async start(
    inputCallback: (audioData: AudioData, frameCount: number) => Promise<void>,
  ): Promise<void> {
    // Initialize your audio recording library
    // Call inputCallback with a Uint8Array of 16-bit PCM data
  }

  async output(audio: Uint8Array, sampleRate?: number): Promise<void> {
    // Play 16-bit PCM mono through the speaker
  }

  interrupt(): void {
    // Stop all queued and playing audio (user barge-in)
  }

  async stop(): Promise<void> {
    // Release microphone and audio resources
  }
}
```

Recommended libraries:

* [react-native-audio-api](https://github.com/software-mansion/react-native-audio-api) — low-level audio API
* [expo-av](https://docs.expo.dev/versions/latest/sdk/av/) — Expo projects
* [react-native-live-audio-stream](https://github.com/niceDev0908/react-native-live-audio-stream) — real-time streaming

## Session methods

| Method                                 | Description                                                                                                 |
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `await agent.start()`                  | Fetch a signed WebSocket URL and connect                                                                    |
| `await agent.waitForConnect(timeout?)` | Wait until connected; `timeout` is in seconds (returns `false` on timeout)                                  |
| `await agent.sendAudio(data)`          | Send a raw PCM audio chunk                                                                                  |
| `agent.isConnected()`                  | Current connection status                                                                                   |
| `agent.getInteractionId()`             | The current interaction (call) ID, once connected                                                           |
| `agent.getState()`                     | Current `AgentState`                                                                                        |
| `agent.mute()` / `agent.unmute()`      | Mute or unmute the microphone without disconnecting. While muted, the SDK sends silence so VAD stays stable |
| `agent.isMuted()`                      | Current mute status                                                                                         |
| `await agent.waitForDisconnect()`      | Wait until the session ends                                                                                 |
| `await agent.stop()`                   | Close the connection and clean up                                                                           |

> **Note**
>
> Call `await agent.stop()` on unmount so the WebSocket and audio interface are cleaned up. Reconnection is not supported: each WebSocket URL is single-use. If the connection drops, call `stop()` and create a new `ConversationAgent`.

Stop or recreate the agent when the app backgrounds. React Native suspends WebSockets in the background, and you cannot reuse the old URL:

```typescript
import { useEffect, useRef } from "react";
import { AppState, AppStateStatus } from "react-native";

const appState = useRef(AppState.currentState);

useEffect(() => {
  const subscription = AppState.addEventListener(
    "change",
    (nextState: AppStateStatus) => {
      if (appState.current.match(/active/) && nextState === "background") {
        agentRef.current?.stop();
      }
      appState.current = nextState;
    },
  );

  return () => {
    subscription.remove();
    agentRef.current?.stop().catch(console.error);
  };
}, []);
```

## Troubleshooting

**Microphone permission denied.** Request the microphone before starting a voice session.

Expo:

```typescript
import { Audio } from "expo-av";

const { granted } = await Audio.requestPermissionsAsync();
if (!granted) {
  // Ask the user to enable the permission in Settings
}
```

Bare React Native, with [react-native-permissions](https://github.com/zoontek/react-native-permissions):

```typescript
import { request, PERMISSIONS, RESULTS } from "react-native-permissions";
import { Platform } from "react-native";

const result = await request(
  Platform.OS === "ios"
    ? PERMISSIONS.IOS.MICROPHONE
    : PERMISSIONS.ANDROID.RECORD_AUDIO,
);

if (result !== RESULTS.GRANTED) {
  // Handle denied permission
}
```

**No audio output.** Check device volume and the iOS silent switch. Confirm `output()` writes to the speaker at `output_sample_rate`.

**Connection timeout.** Check connectivity, API key, `org_id` / `workspace_id` / `app_id`, and that the agent has a committed version.

> **Warning**
>
> Never embed your API key in the app binary. Point `baseUrl` at a backend you control, leave `apiKey` empty, and pass your app auth in `customHeaders` (cookies are not available in React Native):

```typescript
const agent = new ConversationAgent({
  apiKey: "",
  baseUrl: "https://api.yourapp.com/sarvam/",
  customHeaders: {
    Authorization: "Bearer eyJhbGciOiJIUzI1NiIs...",
    "X-User-Id": "user-123",
  },
  platform: "react-native",
  audioInterface: new MyRNAudioInterface(),
  config: {
    // ...
  },
});
```