<!-- AvatarFactory Docs · https://avatarfactory.in/docs/guides/byok · Full map: https://avatarfactory.in/llms.txt -->

# Bring your own keys

Run the voice or the model on your own vendor account. How your keys are encrypted, where they are used, and what never leaves our servers.

By default your avatars speak and think on **AvatarFactory's** provider accounts,
metered against your plan. Bring-your-own-key (BYOK) points a slot at **your**
account with a vendor instead — so the vendor bills you directly, and you get
access to models and voices we do not resell.

There are two independent slots. You can bring a key for one, both, or neither.

| Slot | Providers | What your key pays for |
| --- | --- | --- |
| Voice | ElevenLabs, Inworld | The speech synthesis. Calls on your own voice key do not draw platform minutes. |
| Brain | OpenAI, Anthropic, Google | The language model only. Platform minutes still apply — you are paying the model vendor, not replacing the session. |

> **Voice BYOK and brain BYOK are metered differently, and the difference
>   matters.** Your own *voice* key exempts those calls from platform minutes.
>   Your own *brain* key covers the model cost, but the session is still metered
>   as normal. Do not budget as though brain BYOK makes a session free.

> **Groq never takes a customer key.** It is a host for open-weights models, not
>   a vendor account you hold, so there is nothing to bring. Groq models always run
>   on the platform tier.

## How your keys are protected

This is the part worth being precise about, so here is exactly what happens to a
provider key from the moment you paste it.

### Step 1 — Validated in the request, never logged

The key is checked against the shape that provider issues — `sk_` for
ElevenLabs, `sk-` for OpenAI, `sk-ant-` for Anthropic, `AIza` for Google, and a
minimum length for Inworld's opaque credentials. A malformed key is rejected
before anything is written, with an error naming the expected shape.

### Step 2 — Encrypted with AES-256-GCM before it is stored

The plaintext key never reaches the database. It is sealed with **AES-256-GCM**
using a 256-bit server key held only in the server environment — never in the
codebase, never in a client bundle.

Every secret gets its **own random 96-bit initialization vector**, so two
identical keys encrypt to different ciphertext. GCM is authenticated encryption:
an **authentication tag** is stored alongside the ciphertext, so tampering with
the stored bytes makes decryption fail loudly instead of silently returning
garbage.

### Step 3 — Tagged with the key that sealed it

Each stored credential records **which** encryption key sealed it. That is what
makes rotation possible: we can introduce a new server key and roll forward
without a bulk re-encryption pass over existing secrets, and without a window
where old credentials cannot be read.

### Step 4 — Never returned — not even partially

Once saved, the API that lists your credentials returns the label, the provider,
the status, and the dates. It does **not** return the secret, the ciphertext, or
even a prefix — a provider key's prefix can itself be identifying, so we do not
show one. The only way to change a key is to add a new one and delete the old.

### Where your key is and is not used

| Component | Sees your provider key? |
| --- | --- |
| Our call server, at session time | Yes — decrypted in memory for the duration of the call to authenticate to the vendor | and never persisted in plaintext. |
| The SDK in your user's browser or app | No. The key is resolved server-side from your account. It never travels through the SDK | the config object | or the WebSocket. |
| A deployment or Framer embed | No. The binding lives on the avatar | so a public embed with no credential in its request path still resolves to the right account. |
| Our dashboard, after you save it | No. The browser never receives it back. |
| Logs and error reports | No. Audit entries record that a key was saved and which provider it was for — never the value. |

> The SDK flags `useOwnVoice` and `useOwnBrain` are **switches, not carriers**.
>   They tell the server "bill this to my account"; the server does the lookup. A
>   key in your client config would be a key in your client bundle, which is why
>   the API is shaped this way.

## Adding a key

**Profile → Developer → Your provider keys → Add provider key.**

Pick the provider, give the key a name you will recognise later, and paste the
secret. Each provider's hint tells you where to find it in that vendor's own
dashboard.

You can hold **as many keys per provider as you like** — a separate one per
environment or per client, say. The first key you add for a provider
automatically becomes that provider's **default**, so adding exactly one key
never requires discovering that a second step exists. Use **Set as default** to
move it later.

You can also add a key **inline** while configuring an avatar: the Brain and
Voice panels offer **Add key** at the point you need one, and the key you add is
bound to that slot immediately rather than making you go and reselect it.

### Managing keys

| Action | Effect |
| --- | --- |
| Disable | The key stops being selectable and stops being used, but stays on the account. Reversible — re-enable it at any time. |
| Set as default | Makes it the key a slot resolves to when it is set to “Platform key (or your default)”. One default per provider. |
| Delete | Permanent. If avatars are still bound to the key, you are told which before it is removed — deleting anyway leaves those avatars to fall back to the platform key, or to fail if the model is BYOK-only. |

## Choosing which key an avatar uses

Provider keys live on your **account**; which key a given avatar uses is chosen
on the **avatar**. That split is what lets one account run several clients'
avatars on several different vendor accounts.

In the Platform, open an avatar and use the **Brain** or **Voice** tab. The
**API key** field lists your active keys for that provider, plus a **Platform
key** option:

- **Platform key** — run on AvatarFactory's account, metered to your plan. If
  you have set a default for that provider, this option resolves to your default
  instead, and says so.
- **A named key** — run on that specific credential.

Some models are **BYOK-only**: we hold no key for them at all, so the platform
option is not offered and the field is required. The panel tells you which
provider key the model needs and lets you add it there and then.

## In the SDK

You rarely need these — the binding on the avatar is usually the right place, and
it is the only thing a public embed can use. Set them when one integration
should differ from what the avatar was published with.

```tsx
const config = {
  getSessionToken: async () => fetchSessionToken(),
  avatar: {
    avatarId: "your-avatar-id",

    // Bill the model to your own vendor account.
    brain: {
      provider: "anthropic",
      model: "claude-sonnet-4-5",
      useOwnBrain: true,
    },

    // Bill speech to your own account with the TTS provider.
    voiceSettings: {
      provider: "elevenlabs",
      voiceId: "your-voice-id",
      useOwnVoice: true,
    },
  },
};
```

Leaving a flag out is not the same as setting it to `false`. Omit it and the
binding on the avatar decides — that is the normal case, and the only one an
embed can use. `true` forces your key; `false` vetoes it for that integration
and runs on the platform key even if the avatar binds one.

When you have asked for your key — either by setting the flag to `true` or by
binding one on the avatar — and no usable credential is found, the session is
**rejected** rather than quietly moved onto the platform key, so a deleted or
disabled credential surfaces instead of silently changing who is billed. Only an
avatar with no binding at all falls back to the platform key.

Bring-your-own-key requires a plan that includes it, and is not available on test
keys. Sessions on a plan without it run on the platform key; on a BYOK-only
model, where there is nothing to fall back to, they are rejected.

> A key you bring is a key you are billed for, directly by that vendor, outside
>   your AvatarFactory plan. Set spend limits in the vendor's own dashboard —
>   AvatarFactory cannot cap spending on an account it does not own.

## Related

- [API keys](/docs/platform/api-keys) — the `af_test_*` and `af_live_*` keys that
  authenticate *you* to *us*. Different thing, different rules.
- [Authentication](/docs/authentication) — keeping your live key server-side.
