<!-- AvatarFactory Docs · https://avatarfactory.in/docs/sdk/react-native/avatar-provider · Full map: https://avatarfactory.in/llms.txt -->

# AvatarProvider

Wrap your app once. It connects to the avatar, holds the session, and shares it with every component inside.

`AvatarProvider` is where you connect and configure. You give it a `config` object — how to authenticate, which avatar to load, and which mode to run — and it makes the avatar available to `` and the `useAvatar` hook anywhere below it.

## Basic usage

```tsx
import { AvatarProvider } from "@avatarfactory/react-native";

const config = {
  getSessionToken: async () => {
    const res = await fetch("https://your-backend.com/api/avatar-token", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ avatarId: "default" }),
    });
    const { sessionToken } = await res.json();
    return sessionToken;
  },
  mode: "call",
  avatar: {
    avatarId: "default",
  },
};

export default function App() {
  return (
    <AvatarProvider config={config}>
      {/* your screens */}
    </AvatarProvider>
  );
}
```

## Authentication

For production on React Native, use **`getSessionToken`** — your app never embeds a live key; it calls your backend, which mints a short-lived, single-use session token. For local development you may use **`apiKey`** with a **test key** (`af_test_*`).

> The `apiKey` method accepts **test keys only** (`af_test_*`), for local development. Live keys (`af_live_*`) are rejected — a shipped app bundle can be decompiled, so a live key inside one is effectively public. For production, always go through `getSessionToken`. (`deployId` is not used in mobile apps.)

### getSessionToken

A session token is a short-lived credential that works for a single connection. Because each token is used once, you give the SDK a function that fetches a fresh one — not a token string. The SDK calls this function every time it connects (the first time, and again on every reconnect), so each connection gets its own token and your secret key stays on your server.

```tsx
<AvatarProvider
  config={{
    getSessionToken: async () => {
      const res = await fetch("https://your-backend.com/api/avatar-token", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ avatarId: "default" }),
      });
      const { sessionToken } = await res.json();
      return sessionToken;
    },
    avatar: { avatarId: "default" },
  }}
>
  {/* ... */}
</AvatarProvider>
```

**Contract:**

- The callback must return `Promise` resolving to a non-empty token.
- It receives no arguments — capture any context (auth headers, user id) in a closure.
- If the fetch rejects or the resolved value is empty, the SDK emits a `SESSION_TOKEN_FETCH_FAILED` error and aborts the connect attempt before any WebSocket is opened.
- Tokens are not cached by the SDK; the callback runs on every connect.

### Minting tokens on your backend

The `getSessionToken` callback hits **your** backend, which exchanges your secret API key for a fresh token by calling the AvatarFactory token endpoint:

```
POST https://api.avatarfactory.in/v1/session/token
Authorization: Bearer <YOUR_SECRET_API_KEY>
Content-Type: application/json

{ "avatarId": "default" }
```

A minimal route handler (Next.js shown; any backend works) that keeps your key server-side:

```ts
// app/api/avatar-token/route.ts
import { NextResponse } from "next/server";

export async function POST(req: Request) {
  const { avatarId } = await req.json();

  const res = await fetch("https://api.avatarfactory.in/v1/session/token", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.AVATAR_FACTORY_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ avatarId }),
  });

  if (!res.ok) {
    return NextResponse.json({ error: "Failed to mint token" }, { status: res.status });
  }

  // Forward the upstream token payload to the SDK callback.
  return NextResponse.json(await res.json());
}
```

> Authenticate this route with your app's own session (the logged-in user) before minting a token — that's where you enforce who is allowed to start an avatar session and how often.

## Config reference

`AvatarProvider` accepts a single `config` prop of type `AvatarConfig`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `getSessionToken` **(required)** | `() => Promise<string>` | — | Callback returning a fresh, single-use session token. Invoked on every connect. Required for production; either getSessionToken or apiKey (dev) must be supplied. |
| `apiKey` | `string` | — | Test key (af_test_*) for local development only. Live keys (af_live_*) are rejected — use getSessionToken for production. |
| `avatar.avatarId` **(required)** | `string` | — | The ID of the avatar character to load. |
| `avatar.layout` | `Record<string, string>` | — | Platform-agnostic layout key/value pairs interpreted by the native renderer (e.g. fit, alignment). |
| `avatar.systemPrompt` | `string` | — | Per-session context — who the end user is and why they are here. Call mode. Not the avatar's identity: set that on the platform. |
| `avatar.brain` | `AvatarBrain` | — | Which model powers the conversation: { provider, model?, useOwnBrain? }. provider is server-validated, so newer vendors work without an SDK release. |
| `avatar.languages` | `"auto" \| AvatarLanguage[]` | — | Languages the conversation may use. "auto" lets the server detect and switch. Omitted means English. |
| `avatar.voiceSettings` | `AvatarVoiceSettings` | — | Voice provider, voice, and tuning: provider, voiceId, useOwnVoice, visimeMs, voiceSpeed, voiceStability, similarityBoost. |
| `avatar.voiceSettings.provider` | `"elevenlabs" \| "inworld"` | — | Which speech engine voices the avatar. Choosing one needs no setup — it runs on your plan's included minutes with a default voice. |
| `avatar.voiceSettings.useOwnVoice` | `boolean` | `unset — the avatar's binding decides` | Route speech through your own account with that provider. Requires a saved provider key, and a plan that includes BYOK; the key is resolved server-side and never travels through the app. Omit it to follow the avatar's binding; false vetoes that binding for this integration. |
| `avatar.greeting` | `GreetingConfig` | — | An opening line spoken once the session goes live: { enabled, message?, language? }. Text, not pre-rendered audio. |
| `mode` | `"call" \| "tts" \| "audio" \| "player"` | `"call"` | Operating mode. See Modes for details. |
| `turnTaking.mode` | `"auto" \| "push-to-talk"` | `"auto"` | Who decides a turn is over. Call mode only. |
| `controls.enabled` | `boolean` | `false` | Show the built-in start / stop / mute bar. Hidden unless explicitly true. |
| `controls.stopSpeaking` | `boolean` | `false` | Add a \"stop talking\" button to the built-in bar, shown only while the avatar speaks. |
| `perception.camera` | `boolean` | `false` | Allow the avatar to see. Opt-in, and even then the camera opens just-in-time — never on connect. |
| `transcript.enabled` | `boolean` | `false` | Assemble a live transcript and render captions. Off means none is built at all. |
| `statusBanner.enabled` | `boolean` | `true` | Built-in connectivity banner, driven by live stream health. |
| `connectTone.enabled` | `boolean` | `false` | Ringback tone while connecting. connectTone.src is ignored on mobile — the built-in tone is always used. |
| `debug` | `boolean` | `false` | Enable verbose console logging during development. |

> Full types and defaults for every field are in
>   [Configuration](/docs/sdk/react-native/configuration), including the table of
>   [where mobile differs from web](/docs/sdk/react-native/configuration#where-mobile-differs-from-web).

## Config deep-dives

### avatar.layout

Customise how the avatar fits its view using platform-agnostic key/value pairs. The native renderer interprets these — common keys are `fit` and `alignment`.

```tsx
const config = {
  getSessionToken: async () => fetchSessionToken(),
  avatar: {
    avatarId: "default",
    layout: {
      fit: "cover",
      alignment: "center",
    },
  },
};
```

### avatar.voiceSettings

Fine-tune the voice for a session. `visimeMs` sets the viseme grouping window — visemes within this many milliseconds are merged into one mouth shape; the rest are passed to the voice engine.

```tsx
const config = {
  getSessionToken: async () => fetchSessionToken(),
  avatar: {
    avatarId: "default",
    voiceSettings: {
      visimeMs: 40,         // viseme grouping window (ms); default 40
      voiceSpeed: 1.0,      // passed to the voice engine
      voiceStability: 0.5,  // passed to the voice engine
      similarityBoost: 0.8, // passed to the voice engine
    },
  },
};
```

> Refer to your TTS provider's documentation for valid value ranges of `voiceSpeed`, `voiceStability`, and `similarityBoost`.

### How avatar settings are resolved

Voice, brain, and lip-sync settings can come from two places: what you saved when you **published** the avatar, and what you pass in code. They resolve in this order:

1. **Value passed in the SDK config** — always wins.
2. **Value saved when the avatar was published** — used when the config omits it.
3. **System default** — used when neither is set.

So code overrides the published settings, which override the defaults.

> **Voice selection:** an avatar's voice is normally chosen when you publish it. If no valid voice is selected at publish time (or supplied in code), AvatarFactory falls back to a **default voice** for the selected provider — for both `"elevenlabs"` and `"inworld"` — so the avatar always speaks. An invalid voice also falls back to the default.

### avatar.greeting

An opening line the avatar speaks once the session goes live. You supply
**text**, not audio — omit `message` for the built-in line in that language.

```tsx
const config = {
  getSessionToken: async () => fetchSessionToken(),
  mode: "call",
  avatar: {
    avatarId: "default",
    greeting: {
      enabled: true,
      message: "Hi! What can I help with?",
      language: "en",
    },
  },
};
```

Greeting languages: `en`, `es`, `fr`, `de`, `hi`, `ru`, `pt`, `ja`, `it`.

> Migrating from an older integration that passed a pre-rendered `greeting.job`
>   at the top level? Move it to `avatar.greeting` and pass a `message` string
>   instead.

### avatar.systemPrompt

Context for *this* session — who your user is and what they came for — for `call`
mode, inside the `avatar` object. It applies to that conversation only.

```tsx
const config = {
  getSessionToken: async () => fetchSessionToken(),
  mode: "call",
  avatar: {
    avatarId: "default",
    systemPrompt:
      "You're speaking with a Pro-tier customer, subscribed since 2023. " +
      "They arrived from the billing screen.",
  },
};
```

Keep it short. Because it changes from session to session it cannot be cached the
way the avatar's own configuration is, and it is re-sent on every turn.

> This is not where an avatar's personality or reference material goes. Set those
>   on the avatar itself in the platform — they persist across every session, hold
>   far more text, and stay in effect when you leave `systemPrompt` unset.

### controls

The SDK ships with built-in start/stop buttons for `call` mode, but they are **off by default**. Opt in by setting `controls.enabled` to `true`; otherwise drive sessions yourself with the `useAvatar` hook.

```tsx
// Opt in to the built-in start/stop UI (call mode)
const config = {
  controls: { enabled: true },
  // ...
};
```

### avatar.voiceSettings.provider

Which text-to-speech engine voices the avatar — `"elevenlabs"` (the default) or
`"inworld"`. It lives inside `avatar.voiceSettings`, not at the top level.

```tsx
const config = {
  getSessionToken: async () => fetchSessionToken(),
  avatar: {
    avatarId: "default",
    voiceSettings: { provider: "inworld", voiceId: "your-voice-id" },
  },
};
```

Choosing a provider needs **no setup** — the avatar speaks using your plan's
included minutes and a default voice for that provider. You only bring your own
provider key when you opt into `useOwnVoice`.

### avatar.voiceSettings.useOwnVoice

Route speech through your **own** account with the selected provider.

```tsx
const config = {
  getSessionToken: async () => fetchSessionToken(),
  avatar: {
    avatarId: "default",
    voiceSettings: { provider: "elevenlabs", useOwnVoice: true },
  },
};
```

This is a switch, not a carrier: the key is resolved server-side from your
account and **never travels through the app**. Calls on your own voice key do
not draw platform minutes. See
[Bring your own keys](/docs/guides/byok).

> `useOwnVoice` bills your own account with that vendor, outside your
>   AvatarFactory plan. Save the key under **Profile → Developer** first, and set
>   spend limits in the vendor's own dashboard.

### avatar.brain

```tsx
const config = {
  getSessionToken: async () => fetchSessionToken(),
  avatar: {
    avatarId: "default",
    brain: {
      provider: "anthropic",       // "openai" | "anthropic" | "groq" | server-validated string
      model: "claude-sonnet-4-5",  // omit for the provider's default
      useOwnBrain: true,           // bill the model to your own vendor account
    },
  },
};
```

Unlike voice BYOK, your own **brain** key covers the model cost but the session
is still metered against your plan.

## Common pitfalls

> **Don't nest multiple providers** unless you deliberately want two independent avatar sessions running simultaneously. Each `AvatarProvider` opens its own WebSocket connection.

> **Changing `getSessionToken` or `mode` at runtime triggers a full session reset.** Other fields like `systemPrompt` can be updated dynamically without resetting.
