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

# Authentication

Mint short-lived session tokens from your backend so your secret API key never reaches the browser.

The SDK connects directly to AvatarFactory's servers, so every connection needs a
token. The secure, production-recommended flow is to have **your** backend
exchange your secret API key for a short-lived **session token**, and hand that
token to the SDK. Your key stays on your server; the browser only ever sees a
single-use token.

## How the flow works

### Step 1 — The SDK asks your backend for a token

You give the SDK a `getSessionToken` function. Every time it connects, the SDK
calls your backend endpoint (not ours) to fetch a fresh token.

### Step 2 — Your backend calls the AvatarFactory token endpoint

Your endpoint sends your **secret API key** to AvatarFactory and receives a
short-lived session token in return. This is the only place your key is used.

### Step 3 — Your backend returns the token to the SDK

You forward the token back to the SDK, which uses it to open the connection. The
token is single-use, so this repeats on every connect/reconnect.

> Browser → **your backend** → AvatarFactory. The browser never talks to the
>   token endpoint directly and never sees your API key.

## The token endpoint

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

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

**Request body**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `avatarId` **(required)** | `string` | — | The avatar to start a session for. Use "default" for the public demo avatar, or one of your published avatar IDs. |

**Successful response** — `200 OK`

```json
{ "sessionToken": "<short-lived session token>" }
```

**Common errors**

| Status | Meaning | Fix |
| --- | --- | --- |
| 400 | Bad Request | Missing or malformed avatarId. |
| 401 | Unauthorized | Missing or invalid API key. Check the Authorization header. |
| 403 | Forbidden | Your plan isn't allowed to use this avatar (e.g. a premium avatar on a free plan). |
| 429 | Too Many Requests | Rate limit exceeded — retry after a short delay. |

> Find your API key in the **[Platform → Developer tab](/profile?tab=developer)**.
>   Use a **live** key (`af_live_*`) here — it stays server-side, so it's safe.

## Example: your backend endpoint

Your endpoint receives an `avatarId` from your frontend, calls AvatarFactory with
your secret key, and returns the token. Pick your stack below — it's the same
minimal endpoint, adapt it to your framework.

 {
  const { avatarId } = req.body;

  const upstream = 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 (!upstream.ok) {
    return res.status(upstream.status).json({ error: "Failed to mint token" });
  }

  // Forward { sessionToken } to the SDK
  res.json(await upstream.json());
});`,
  },
  {
    label: "Next.js (Route Handler)",
    lang: "ts",
    code: `// app/api/avatar-token/route.ts

  const { avatarId } = await req.json();

  const upstream = 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 (!upstream.ok) {
    return NextResponse.json(
      { error: "Failed to mint token" },
      { status: upstream.status },
    );
  }

  return NextResponse.json(await upstream.json());
}`,
  },
  {
    label: "Python / FastAPI",
    lang: "python",
    code: `import os, httpx
from fastapi import FastAPI, Request, Response

app = FastAPI()

@app.post("/avatar-token")
async def avatar_token(req: Request):
    body = await req.json()

    async with httpx.AsyncClient() as client:
        upstream = await client.post(
            "https://api.avatarfactory.in/v1/session/token",
            headers={"Authorization": f"Bearer {os.environ['AVATAR_FACTORY_KEY']}"},
            json={"avatarId": body["avatarId"]},
        )

    return Response(content=upstream.text, status_code=upstream.status_code,
                    media_type="application/json")`,
  },
]} />

## Wire it to the SDK

Point the SDK's `getSessionToken` at the endpoint you just built. The SDK reads
`sessionToken` from the response:

```tsx
<AvatarProvider
  config={{
    getSessionToken: async () => {
      const res = await fetch("/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>
```

See [AvatarProvider → Authentication](/docs/sdk/react/avatar-provider#authentication)
for the full `getSessionToken` contract and the `deployId` / `apiKey` alternatives.

> Keep `AVATAR_FACTORY_KEY` in a **server-only** environment variable — no
>   `NEXT_PUBLIC_`, `VITE_`, `REACT_APP_` or `EXPO_PUBLIC_` prefix. Any of those ship
>   the value to the browser, and a live key in your client bundle is effectively
>   public: anyone can open devtools and read it.

> **Call this endpoint from your server, never from the browser.** The rejection of
>   `af_live_*` on the SDK's `apiKey` path is a backstop for that one path — it is not
>   a general guarantee. If you fetch `/v1/session/token` directly from client code
>   with a live key, it will work, and it will publish your key to every visitor. The
>   minting request is the thing that must stay server-side; `getSessionToken` exists
>   so the browser only ever receives the short-lived token that comes back.

> No server to put it on? Use your **test key** with the SDK's `apiKey` option
>   instead — it is scoped to a small separate allowance for exactly this situation.
>   Do not reach for a live key to work around not having a backend. See
>   [API keys](/docs/platform/api-keys).
