> ## Documentation Index
> Fetch the complete documentation index at: https://docs.liveavatar.com/llms.txt
> Use this file to discover all available pages before exploring further.

# AI Sales Agent

> Deploy a self-hosted sales agent to help qualify leads and update your CRM.

This guide walks through setting up your own avatar sales agent, similar to the one we run at [liveavatar.com/ai-sales-agent](https://www.liveavatar.com/ai-sales-agent). It's an avatar that lives on your site, talks to visitors, qualifies them, and writes what it learned to your CRM.

By the end you'll have:

* A [FULL Mode](/docs/full-mode/overview) avatar session running in the browser, on your own domain.
* Your own OpenAI-compatible endpoint driving every turn of the conversation, with a prompt you control.
* A summary of each conversation landing in Notion and Slack the moment the visitor disconnects.

It's a single Next.js app, so all three pieces deploy together.

<Card title="liveavatar-sales-agent" icon="github" href="https://github.com/heygen-com/liveavatar-sales-agent">
  Reference implementation for this guide. Deploys to Vercel; MIT licensed.
</Card>

Provisioning order matters here, so it's worth reading start to finish the first time through.

## Prerequisites

| Thing                      | Where                                                      |
| -------------------------- | ---------------------------------------------------------- |
| LiveAvatar API key         | [app.liveavatar.com](https://app.liveavatar.com)           |
| Somewhere public to deploy | Vercel is assumed below; anything publicly reachable works |
| Node 22+                   | For local development                                      |

You do **not** need your own avatar. The repo ships the public demo avatar id, which any account can use. Notion and Slack are optional.

## Quickstart

```bash theme={null}
git clone https://github.com/heygen-com/liveavatar-sales-agent
cd liveavatar-sales-agent
npm install

npm run setup        # asks for your API key, provisions what's missing, writes .env.local
npm run dev          # http://localhost:3003
```

Open [localhost:3003](http://localhost:3003), start a session, and the avatar should appear and greet you by name.

`npm run setup` picked an avatar, generated the signing secret, and wrote it all to `.env.local`. It also spins up a basic [context](/docs/core-concepts/contexts) that supplies the greeting and can help with the remaining pieces of this integration. It inspects your current configuration and is safe to rerun. Every variable it writes is documented in the repo's `.env.example`.

What you're talking to right now is your account's **default LLM**, not this app's sales prompt. Getting that swapped in is the rest of the guide — and it needs a URL LiveAvatar can reach, which localhost isn't. So: deploy, then wire up the brain.

<Note>
  `npm run setup` warns that `ANTHROPIC_API_KEY` is missing. Ignore it for now — nothing you've run so far needs it. It comes in when you connect the brain.
</Note>

## Connecting your personal brain

So far all we've done is spin up a quick LiveAvatar setup. Next we connect it to your own LLM, so the agent answers from your prompt and your data instead of the account default.

For this demo we provide a basic [custom LLM](/docs/full-mode/custom-llm) setup that we'll deploy to Vercel. It's the `/api/chat/completions` route in the repo — an OpenAI-compatible endpoint wrapping a basic Anthropic model, which assembles the sales prompt on every turn before it answers. Swap the model or the prompt later; for now, deploy it as-is by following the steps below.

<Steps>
  <Step title="Add your Anthropic API key">
    The endpoint needs a model to call. Grab a key from [console.anthropic.com](https://console.anthropic.com) and add it to `.env.local`:

    ```bash theme={null}
    ANTHROPIC_API_KEY=sk-ant-...
    ```

    The same key writes the post-call summary later.
  </Step>

  <Step title="Deploy">
    ```bash theme={null}
    vercel link      # create a new project
    vercel --prod    # succeeds with no env vars set
    ```

    Framework preset is **Next.js**; it's a standalone app, so no root-directory config.

    <Note>
      Deploy before provisioning. LiveAvatar calls your endpoint over the public internet, so the LLM configuration has to point at a URL that already exists — which means the deployment comes first. The build reads no configuration, so deploying with nothing set works fine.
    </Note>
  </Step>

  <Step title="Provision against the deployment">
    ```bash theme={null}
    npm run setup -- --url https://your-app.vercel.app
    ```

    This registers a secret and creates the LLM configuration pointing at your deployment, then writes both ids to `.env.local`. See [what it creates](#what-the-setup-creates) if you'd rather do it by hand.

    Use the **production domain, not a preview URL**. Preview URLs change per commit, so a configuration pointed at one breaks silently on your next push.
  </Step>

  <Step title="Copy the env vars into Vercel">
    `.env.local` is gitignored and never deploys. **Project → Settings → Environment Variables** accepts a pasted `.env` file, so paste the contents of `.env.local` — including the Anthropic key — and review before saving. Skip `VERCEL_OIDC_TOKEN` if present; Vercel injects that itself.
  </Step>

  <Step title="Redeploy">
    ```bash theme={null}
    vercel --prod    # env changes only apply to new deployments
    ```
  </Step>
</Steps>

Restart your dev server (`npm run dev`) so it picks up the new `.env.local`, then start a session — or use the deployed URL directly, either works.

Your LiveAvatar session now calls your deployed endpoint on every turn instead of the account default LLM, so the agent is answering from your prompt. This holds even when you mint from localhost: LiveAvatar always calls the public `base_url`, so the brain requests show up in your **deployment's** logs, never your terminal.

See [The prompt](#the-prompt) for how that prompt is put together and where to edit it.

### The prompt

The system prompt is assembled from the Markdown files in `src/lib/ai-sales/brain/prompt-parts/`, concatenated in filename order:

```
00_prompt_outline.md      06_customer_stories.md
01_agent_info.md          07_competitor_info.md
02_communication_style.md 08_conversation_flow.md
03_response_guidelines.md 09_resources.md
04_product_info.md        10_guardrails.md
05_pricing.md             11_prospect_context.md
```

* Every `*.md` in the directory is a part — adding or removing one needs no code change.
* Files prefixed with `_` are ignored, so a part can be parked without deleting it.
* YAML frontmatter and HTML comments are stripped, so `<!-- … -->` notes are documentation for you, not input for the model.
* `{{AGENT_NAME}}`, `{{AGENT_ROLE}}`, `{{PRODUCT_NAME}}` and `{{COMPANY_NAME}}` are substituted from env vars.

Several parts ship as scaffolds with an `OPERATOR SCAFFOLD` comment marking where your pricing, competitor handling, and customer stories go. Set `PROMPT_PARTS_DIR` to replace the bundled prompt wholesale at deploy time — useful for keeping a real sales prompt outside the repo.

### What the setup creates

Two objects on the LiveAvatar side — a **secret** holding the key that protects your endpoint, and an **LLM configuration** pointing at it:

```bash theme={null}
# 1. Register the key that protects your endpoint. You invent this value.
openssl rand -hex 32

curl -X POST https://api.liveavatar.com/v1/secrets \
  -H "X-API-KEY: $LIVEAVATAR_API_KEY" -H 'Content-Type: application/json' \
  -d '{"secret_type":"OPENAI_API_KEY","secret_name":"sales-agent-brain","secret_value":"<the value you generated>"}'

# 2. Point an LLM configuration at your deployment.
curl -X POST https://api.liveavatar.com/v1/llm-configurations \
  -H "X-API-KEY: $LIVEAVATAR_API_KEY" -H 'Content-Type: application/json' \
  -d '{"secret_id":"<from step 1>","display_name":"sales-agent","model_name":"sales-agent","base_url":"https://your-app.vercel.app/api"}'
```

`secret_type` is `OPENAI_API_KEY` because the endpoint speaks the OpenAI chat/completions protocol. The `secret_value` goes in `AI_SALES_LLM_CONFIG_API_KEY` and the returned configuration id in `LLM_CONFIGURATION_ID`. At runtime LiveAvatar presents the key back as `Authorization: Bearer <value>` and the app compares it in constant time.

<Warning>
  **`base_url` is the base, not the route.** LiveAvatar appends `/chat/completions` itself, exactly as an OpenAI client would. Passing the full route produces a doubled path that 404s — the avatar speaks its opening line, then goes silent, with nothing in your logs, because the request never reaches a function.
</Warning>

Leave `LLM_CONFIGURATION_ID` unset and sessions fall back to your account's default LLM. See [Custom LLM](/docs/full-mode/custom-llm) for the full endpoint contract.

## After the call

While we don't do it in this walkthrough, you can set up configurations to run once the session ends. For example, we configure our calls to add entries in our Notion CRM database and fire an alert to our Slack channels. `npm run setup` doesn't provision either — they're your own workspaces.

**Notion.** Create an internal integration at [notion.so/my-integrations](https://www.notion.so/my-integrations), share your database with it, then set `NOTION_TOKEN` and `NOTION_DATABASE_ID`. The app upserts by email — one page per lead, each conversation appended — so the database needs matching properties. Duplicate the published template instead of building the schema by hand:

<Card title="LiveAvatar Sales Agent — Notion template" icon="table" href="https://heygen.notion.site/liveavatar-sales-agent-template?v=725449792c6983be965d883ac3eaa894">
  The CRM schema this app writes to.
</Card>

**Slack.** Create an incoming webhook and set `SLACK_WEBHOOK_URL`. The webhook is bound to one channel at creation time, so the destination is chosen there, not in the app. Notion runs first so its page link can be included in the Slack message.

## Productionization next steps

What ships here is a demo — enough to have a real conversation and see a lead land in a CRM. A few directions to take it from there:

**Move off the Vercel deployment.** The brain is a serverless route because that's the fastest thing to stand up. A dedicated backend gives you persistent connections, no cold starts on the first turn of a conversation, and somewhere to put the rest of your sales stack. The contract with LiveAvatar doesn't change — it's still an OpenAI-compatible endpoint behind a `base_url`.

**Inject your own data into the prompt.** The demo assembles the prompt from static Markdown plus whatever the visitor types. The interesting version pulls from what you already know about them — your CRM, a Typeform they filled out, product usage, the page they came in from — and drops it into the prompt before the first turn.

**Fire workflow automations on session end.** Notion and Slack are two examples of the same idea: the session-end route hands you a structured summary of the conversation. Send the visitor a follow-up email, open a deal in your CRM, page an account exec if the lead qualified, kick off a nurture sequence.

## Appendix: How a session flows

```mermaid theme={null}
graph TB
    Browser["Visitor's browser"]
    Mint["/api/ai-sales/session"]
    LA["LiveAvatar<br/>(FULL Mode session)"]
    Brain["/api/chat/completions<br/>(your deployment)"]
    End["/api/ai-sales/session-end"]
    CRM["Notion + Slack"]

    Browser -->|"1. request token"| Mint
    Mint -->|"2. POST /v1/sessions/token"| LA
    LA -->|"3. token + room"| Browser
    LA -->|"4. each turn"| Brain
    Brain -->|"SSE"| LA
    Browser -->|"5. on disconnect"| End
    End --> CRM
```

1. **Mint.** `/api/ai-sales/session` calls `POST /v1/sessions/token` with your `LIVEAVATAR_API_KEY`, in FULL Mode, passing the avatar, the context, and the LLM configuration id. At the concurrency ceiling or out of credits it returns `503 { error: { code: "busy" } }` and the UI shows a retry state.
2. **Connect.** The browser joins the room with the Web SDK. The avatar immediately speaks the context's `opening_text` — before any model is in the loop.
3. **Converse.** LiveAvatar calls your `/api/chat/completions` over the public internet on every turn, authenticated with a bearer key you registered as a secret. The route assembles the persona, real-world context (date, weather), an optional lead profile, and any prior CRM history for that email, then streams the model's reply back as OpenAI-shaped SSE.
4. **Close.** On disconnect the browser posts to `/api/ai-sales/session-end` with an HMAC signature; the route summarizes, upserts the Notion page, then posts to Slack with the page link.
