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

# Configuration Reference

Every knob and switch in one place — the complete AvatarConfig object, field by field.

## The shape

`AvatarConfig` splits into two halves: **session wiring** (how this session runs)
at the top level, and the **agent definition** (what the avatar is) under
`avatar`. The same avatar can run hands-free in one app and push-to-talk in
another, which is why turn taking is not part of the avatar.

It is also a discriminated union — you must supply **exactly one** of `apiKey`,
`getSessionToken`, or `deployId`. Supplying more than one is a TypeScript error.

```typescript
type AvatarConfig = AvatarConfigBase &
  (
    | { apiKey: string; getSessionToken?: never; deployId?: never }
    | { getSessionToken: () => Promise<string>; apiKey?: never; deployId?: never }
    | { deployId: string; apiKey?: never; getSessionToken?: never }
  );

type AvatarConfigBase = {
  // --- Session wiring: how this session runs ---
  mode?: AvatarMode;                        // default "call"
  turnTaking?: TurnTakingConfig;
  controls?: { enabled: boolean; stopSpeaking?: boolean };
  perception?: { camera?: boolean };
  transcript?: TranscriptConfig;
  statusBanner?: StatusBannerConfig;
  thinkingIndicator?: ThinkingIndicatorConfig;
  connectTone?: ConnectToneConfig;
  debug?: boolean;

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

> Set `avatar.avatarId` to `"default"` to use our public demo avatar while you
>   build. For production, **[publish your own avatar](/docs/platform/avatars)**
>   and use its ID.

## Session wiring

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `getSessionToken` **(required)** | `() => Promise<string>` | — | Callback returning a fresh, single-use session token. Invoked on every connect. Exactly one of getSessionToken, apiKey, or deployId is required. Recommended for production. |
| `deployId` **(required)** | `string` | — | Public deployment ID (dep_…) — no backend needed. Domain-locked and budget-capped, so it is safe to ship to the browser. |
| `apiKey` **(required)** | `string` | — | Test key (af_test_*) for local development only. Live keys (af_live_*) are rejected by this path — use getSessionToken in production. |
| `mode` | `"call" \| "tts" \| "audio"` | `"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. |
| `turnTaking.spacebar` | `boolean` | `true` | Let the user hold the spacebar as well as the button. Web only. Set false when your page binds space itself — the SDK swallows the key while connected. |
| `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. useAvatar().stopSpeaking() is always available for a custom one. |
| `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. See Camera perception. |
| `transcript.enabled` | `boolean` | `false` | Assemble a live transcript. Off means none is built at all. See Transcript & captions. |
| `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, corners plus top/bottom centre. |
| `transcript.maxEntries` | `number` | `50` | Transcript lines retained. |
| `statusBanner.enabled` | `boolean` | `true` | Built-in connectivity banner, driven by live stream health. On by default; it only appears when something is wrong. |
| `statusBanner.position` | `StatusBannerPosition` | `"bottom-left"` | Anchor corner. Corners only — no centre anchors. |
| `thinkingIndicator.enabled` | `boolean` | `true` | Animated dots shown only while a turn runs unusually slow. A liveness cue, not a warning. |
| `thinkingIndicator.position` | `CaptionPosition` | `"top-left"` | Anchor. A top corner stays clear of custom controls at the bottom. |
| `connectTone.enabled` | `boolean` | `false` | Play a ringback tone while the session connects. Stops once the avatar is painted and the session is live. |
| `connectTone.src` | `string` | — | URL or data URI for your own tone. Omit for the built-in one. Web only — React Native ignores it. |
| `connectTone.loop` | `boolean` | `true` | Repeat the tone until the connect completes. |
| `connectTone.volume` | `number` | `0.4` | 0–1. The default sits under the conversation, not over it. |
| `connectTone.fadeOutMs` | `number` | `350` | Fade-out on stop, in milliseconds. A hard cut clicks. |
| `debug` | `boolean` | `false` | Verbose console logging. Disable before shipping. |

## Agent definition

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `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 mid-conversation; an array restricts it. 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 rendering hints for the canvas, interpreted per platform. Usually left to what the avatar was published with. |

## Field details

### Authentication

Three ways in. For production, always `getSessionToken` — your key never leaves
the backend and tokens expire in seconds.

```tsx
// ✅ Production: your backend issues short-lived tokens
const config = {
  getSessionToken: async () => {
    const res = await fetch("/api/avatar-token");
    const { sessionToken } = await res.json();
    return sessionToken;
  },
  avatar: { avatarId: "default" },
};
```

```tsx
// 🌐 Public demos, Framer, landing pages: no backend, safe to ship
const config = {
  deployId: "dep_...",
  avatar: { avatarId: "default" },
};
```

```tsx
// ⚠️ Development only: test keys (af_test_*). Live keys are rejected here.
const config = {
  apiKey: process.env.NEXT_PUBLIC_AF_TEST_KEY,
  avatar: { avatarId: "default" },
};
```

The `getSessionToken` callback runs on **every** connect — initial and every
reconnect. Tokens are single-use, so mint a fresh one each call. See
[Authentication](/docs/authentication) for backend implementations, and
[API keys](/docs/platform/api-keys) for which credential belongs where.

### turnTaking

`mode` is what kind of conversation this is; `turnTaking` is who ends a turn
inside it.

```ts
turnTaking: { mode: "push-to-talk", spacebar: true }
```

In `"auto"` the server's endpointer decides from the audio. In
`"push-to-talk"` the user holds a button — read `isPushToTalk` and drive it with
`startTalking()` / `stopTalking()` from [useAvatar](/docs/sdk/react/use-avatar),
or let the built-in controls render the button for you.

> While connected in push-to-talk, the SDK listens for the spacebar. If your page
>   already binds space, set `spacebar: false` or the two will fight.

### 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 deliberately 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 from
your account and never passes through the SDK. See
[Bring your own keys](/docs/guides/byok).

> Which models are available depends on your plan, and some are **BYOK-only**.
>   The Platform's Brain tab shows the live catalogue with availability; a model
>   that is not offered there will be rejected at call time.

### avatar.voiceSettings

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

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `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. |
| `voiceId` | `string` | — | A specific voice on that provider. |
| `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. Omit it to follow the avatar's binding; false vetoes that binding for this integration. Calls on your own voice key do not draw platform minutes. |
| `visimeMs` | `number` | `40` | Viseme grouping window. Mouth shapes closer together than this are merged into one. |
| `voiceSpeed` | `number` | — | Passed through to the voice engine. |
| `voiceStability` | `number` | — | Passed through to the voice engine. |
| `similarityBoost` | `number` | — | Passed through to the voice engine. |

> 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 the avatar speaks once the session goes live. You do not supply
audio — give it text, or let it use the built-in line for a language.

```ts
avatar: {
  avatarId: "default",
  greeting: {
    enabled: true,
    message: "Hi! I'm Luna. What are you working on?", // omit for the built-in line
    language: "en",
  },
},
```

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

> A greeting is speech, and browsers block speech that did not follow a user
>   gesture. Start the session from a real click, or the avatar will greet an
>   audience that cannot hear it.

### 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. Note the greeting list is the same set **minus Dutch**.

### connectTone

The "waiting for someone to pick up" ringback, for products where connecting
should feel like placing a call.

```ts
connectTone: {
  enabled: true,
  src: "/sounds/ringback.mp3",  // omit for the built-in tone
  loop: true,
  volume: 0.4,
  fadeOutMs: 350,
},
```

It stops once the avatar is painted **and** the session is live — not when
either one lands first, so it never cuts out into silence while the user waits.
There is also a hard ceiling of **15 seconds**, so a wedged load cannot leave
the tone droning indefinitely.

> When a greeting is enabled too, the avatar waits **500 ms** after the session
>   goes live before speaking, so the ringback's fade can finish. Without the beat
>   an instant greeting sounds clipped.

### How avatar settings are resolved

Voice, brain, language, and lip-sync settings can come from two places. They
resolve in a fixed order:

| Priority | Source | Notes |
| --- | --- | --- |
| 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 | Used when neither is set. |

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

### Dynamic config

Pass new values to the provider to update them live.

**Changing auth (`apiKey` / `getSessionToken` / `deployId`) 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 onClick={() => setPrompt("Grumpy assistant")}>Change mood</button>
  <div style={{ width: 400, height: 400 }}>
    <Avatar />
  </div>
</AvatarProvider>
```

> **Memoise the config object.** A new object literal on every render is a new
>   config every render. `useMemo` it, keyed on the values that actually change,
>   or you will restart the session continuously.
