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

# Configuration

The complete config object you pass to AvatarProvider, field by field — and where mobile differs from web.

## The config object

React Native and web share the **same** configuration type. The only required
pieces are how to authenticate and which avatar to load; everything else is
optional.

It splits into two halves: **session wiring** (how this session runs) at the top
level, and the **agent definition** (what the avatar is) under `avatar`.

```typescript
type AvatarConfig = {
  // --- Authentication ---
  // Production: getSessionToken. Local development only: apiKey (test keys).
  getSessionToken?: () => Promise<string>;
  apiKey?: string;

  // --- Session wiring ---
  mode?: "call" | "tts" | "audio" | "player";  // default "call"
  turnTaking?: { mode?: "auto" | "push-to-talk" };
  controls?: { enabled: boolean; stopSpeaking?: boolean };
  perception?: { camera?: boolean };
  transcript?: TranscriptConfig;
  statusBanner?: StatusBannerConfig;
  connectTone?: ConnectToneConfig;
  debug?: boolean;

  // --- Agent definition ---
  avatar: {
    avatarId: string;
    layout?: Record<string, string>;
    voiceSettings?: AvatarVoiceSettings;
    brain?: AvatarBrain;
    languages?: AvatarLanguages;
    systemPrompt?: string;
    greeting?: GreetingConfig;
  };
};
```

> Use `getSessionToken` for production — **never ship a live key in your app**.
>   Bundles can be inspected, so any key inside one is effectively public. The
>   `apiKey` path is allowed for local development but accepts **test keys only**
>   (`af_test_*`); live keys are rejected by the server.

## Where mobile differs from web

Everything not in this table behaves identically on both platforms.

| Feature | On React Native |
| --- | --- |
| thinkingIndicator | Not available. React Native ships no built-in indicator — read useAvatar().isResponseSlow and render your own. |
| connectTone.src | Ignored. A custom tone file is web-only; React Native falls back to the built-in tone and logs a warning. |
| turnTaking.spacebar | Ignored — there is no spacebar. Push-to-talk itself works normally, via the button. |
| Picture-in-picture | Not available. The camera self-view and hideCameraPreview work as on web; the PiP layout swap does not. |
| deployId | Not a supported mobile path. Mint session tokens on your backend instead. |
| Styling props | Native takes style / captionStyle / statusBannerStyle as ViewStyle. There is no className. |
| Camera permission | A native permission, declared through the Expo config plugin. See Permissions. |

## All fields

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `getSessionToken` **(required)** | `() => Promise<string>` | — | Callback returning a fresh, single-use session token. Invoked on every connect. Required for production; either this 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. |
| `mode` | `"call" \| "tts" \| "audio" \| "player"` | `"call"` | Operating mode. Changing this at runtime triggers a full session reset. |
| `turnTaking.mode` | `"auto" \| "push-to-talk"` | `"auto"` | Who decides a turn is over: the server's endpointer, or the user holding a button. 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 when the server asks — never on connect. |
| `transcript.enabled` | `boolean` | `false` | Assemble a live transcript. Off means none is built at all. |
| `transcript.captions` | `boolean` | `true` | Render the built-in caption overlay. False keeps the data without the UI. |
| `transcript.position` | `CaptionPosition` | `"bottom-center"` | Anchor for the caption overlay — six positions. |
| `transcript.maxEntries` | `number` | `50` | Transcript lines retained. |
| `statusBanner.enabled` | `boolean` | `true` | Built-in connectivity banner, driven by live stream health. It only appears when something is wrong. |
| `statusBanner.position` | `StatusBannerPosition` | `"bottom-left"` | Anchor corner. Corners only. |
| `connectTone.enabled` | `boolean` | `false` | Play a ringback tone while the session connects. |
| `connectTone.loop` | `boolean` | `true` | Repeat the tone until the connect completes. |
| `connectTone.volume` | `number` | `0.4` | 0–1. |
| `connectTone.fadeOutMs` | `number` | `350` | Fade-out on stop. A hard cut clicks. |
| `avatar.avatarId` **(required)** | `string` | — | Which character to load. Use "default" for the public demo avatar, or publish your own and use its ID. |
| `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.languages` | `"auto" \| AvatarLanguage[]` | — | Languages the conversation may use. "auto" lets the server detect and switch. Omitted means English. |
| `avatar.brain` | `AvatarBrain` | — | Which model powers the conversation: { provider, model?, useOwnBrain? }. |
| `avatar.voiceSettings` | `AvatarVoiceSettings` | — | Voice provider, voice, and tuning. See below. |
| `avatar.greeting` | `GreetingConfig` | — | An opening line spoken once the session goes live: { enabled, message?, language? }. |
| `avatar.layout` | `Record<string, string>` | — | Platform-agnostic layout hints (e.g. fit, alignment) interpreted by the native renderer. |
| `debug` | `boolean` | `false` | Verbose console logging. Disable before shipping. |

## Field details

### Authentication

For production, your app calls **your** backend, which exchanges your live key
for a short-lived token.

```tsx
// ✅ Production
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;
  },
  avatar: { avatarId: "default" },
};
```

```tsx
// ⚠️ Local development only — test keys (af_test_*).
const config = {
  apiKey: "af_test_...",
  avatar: { avatarId: "default" },
};
```

The callback runs on **every** connect — initial and every reconnect — and
tokens are single-use, so mint a fresh one each time. See
[Authentication](/docs/authentication) for backend implementations.

### avatar.brain

```ts
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
  },
},
```

`provider` is open-ended — the server validates it, so a newly supported vendor
works without an SDK release. `useOwnBrain` is a switch, not a carrier: the key
is resolved server-side and never passes through the app. See
[Bring your own keys](/docs/guides/byok).

### avatar.voiceSettings

```ts
avatar: {
  avatarId: "default",
  voiceSettings: {
    provider: "elevenlabs",  // "elevenlabs" | "inworld"
    voiceId: "your-voice-id",
    useOwnVoice: false,
    visimeMs: 40,            // viseme grouping window; default 40
    voiceSpeed: 1.0,
    voiceStability: 0.5,
    similarityBoost: 0.8,
  },
},
```

Check your TTS provider's own documentation for valid ranges of `voiceSpeed`,
`voiceStability`, and `similarityBoost` — the SDK passes them straight through.

### avatar.greeting

An opening line spoken once the session goes live. You supply **text**, not
audio.

```ts
avatar: {
  avatarId: "default",
  greeting: {
    enabled: true,
    message: "Hi! What can I help with?", // omit for the built-in line
    language: "en",
  },
},
```

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

> If you are 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.languages

```ts
avatar: { avatarId: "default", languages: "auto" }        // detect and switch
avatar: { avatarId: "default", languages: ["en", "es"] }  // restrict to two
```

Supported: `en`, `es`, `fr`, `de`, `hi`, `ru`, `pt`, `ja`, `it`, `nl`. Omitting
the field means English.

### avatar.layout

Platform-agnostic key/value pairs interpreted by the native renderer.

```tsx
avatar: {
  avatarId: "default",
  layout: { fit: "cover", alignment: "center" },
},
```

### How avatar settings are resolved

1. **The value in your SDK config** — always wins.
2. **The value saved when the avatar was published** — used when the config omits it.
3. **The system default.**

> **Voice fallback.** If no valid voice is selected at publish time or in code,
>   the avatar falls back to the default voice for the chosen provider — on both
>   ElevenLabs and Inworld. An invalid voice ID does the same. The avatar always
>   speaks.

### Dynamic config

**Changing auth (`getSessionToken` / `apiKey`) or `mode` triggers a full session
reset.** Other fields update without one.

```tsx
const [prompt, setPrompt] = useState("Friendly assistant");

const config = useMemo(
  () => ({ ...baseConfig, avatar: { ...baseConfig.avatar, systemPrompt: prompt } }),
  [prompt],
);

<AvatarProvider config={config}>
  <Button title="Change mood" onPress={() => setPrompt("Grumpy assistant")} />
  <View style={{ width: 320, height: 320 }}>
    <Avatar />
  </View>
</AvatarProvider>
```

> **Memoise the config object.** A new object literal on every render is a new
>   config every render, and the provider will keep restarting the session.
