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

# Server-Side ElevenLabs Agents with Anam Avatars

> Build a server-side ElevenLabs Conversational AI agent with a real-time Anam avatar using audio passthrough.

export const elevenLabsAgentPrompt = ["# Build the ElevenLabs Agents demo with an Anam avatar", "", "Build and run the existing Next.js demo from https://github.com/anam-org/anam-cookbook/tree/main/examples/elevenlabs-server-side-agent-nextjs.", "", "Before making changes, read that project's README and source, https://anam.ai/docs/integrations/elevenlabs/agents, and https://anam.ai/docs/llms.txt. Follow the integration overview's links to server setup, starting a session, agent configuration, Director Notes, and client tools. Use the current repository and docs as the source of truth. If you cannot access them, report the limitation rather than inventing APIs or configuration.", "", "## Set up the project", "", "1. Inspect the current workspace. If the demo is already checked out, use it. Otherwise clone https://github.com/anam-org/anam-cookbook.git into a new directory and work in examples/elevenlabs-server-side-agent-nextjs. Do not overwrite an existing application.", "2. Check the project's Node.js requirement and package manager. Follow the README and use the checked-in dependency versions and lockfile.", "3. Copy .env.example to .env.local only if .env.local does not already exist. Have me fill in ANAM_API_KEY, ANAM_AVATAR_ID, ELEVENLABS_API_KEY, and ELEVENLABS_AGENT_ID locally. PERSONA_NAME is optional. Never ask me to paste API keys into chat, print their values, commit them, or put them in browser code.", "4. Explain the dashboard configuration: choose a Cara 4 avatar in Anam Lab; configure an ElevenLabs conversational agent with PCM 16000Hz user input. Anam also supports other PCM sample rates and resamples audio automatically. Select Eleven v4 Turbo (eleven_v4_turbo) for expressive speech with lower latency than Eleven v4 (eleven_v4). Follow the guide's Director Notes setup. Existing v3 Conversational agents use the same audio tags; changing the model in ElevenLabs does not require changes to the Anam session code.", "5. Install dependencies with pnpm install and start the project with pnpm dev. Report the local URL and any remaining configuration I need to complete.", "", "## Preserve the integration", "", "- The server route /api/anam-session fetches an ElevenLabs signed URL and requests an Anam session token with elevenLabsAgentSettings.", "- The browser uses @anam-ai/js-sdk to start the avatar stream. Anam connects to ElevenLabs server-side. Do not add a separate ElevenLabs browser voice session or duplicate microphone/audio playback.", "- Keep the demo's session cleanup, connection errors, Director Notes display, and forwarded client-tool handling.", "- For the client-tool demo, explain how to add the show_notification client tool in the ElevenLabs dashboard, with a required message string and Wait for response enabled. Use the existing handler in the project.", "", "## Verify", "", "Run the project's build and lint commands. With credentials configured, open the app, start a session, grant microphone permission, and verify that the avatar responds with synchronized speech and video. Stop the session and confirm cleanup. Test show_notification and an expressive response using the README's examples. Check that API keys stay server-side.", "", "Report what you actually verified. If credentials, microphone access, or dashboard setup are unavailable, complete the checks you can and list the remaining manual steps. Do not claim a live conversation worked unless you tested it."].join("\n");

export const PrebuiltPrompt = ({name, prompt, promptVersion, title = `Build this ${name} quickstart with a coding agent`, description = "Paste this prompt into Cursor, Claude Code, Codex, or another coding agent.", children}) => {
  const copyPrompt = async event => {
    const button = event.currentTarget;
    if (button.dataset.copyState === "copying") return;
    const label = button.querySelector("[data-copy-label]");
    const status = button.parentElement.querySelector("[data-copy-status]");
    window.clearTimeout(Number(button.dataset.copyResetTimer));
    delete button.dataset.copyResetTimer;
    button.dataset.copyState = "copying";
    button.setAttribute("aria-disabled", "true");
    label.textContent = "Copying…";
    status.textContent = "";
    const copyWithSelection = () => {
      const textArea = document.createElement("textarea");
      textArea.value = prompt;
      textArea.setAttribute("readonly", "");
      textArea.style.position = "fixed";
      textArea.style.opacity = "0";
      document.body.appendChild(textArea);
      try {
        textArea.select();
        if (!document.execCommand("copy")) throw new Error("Copy command failed");
      } finally {
        textArea.remove();
        button.focus({
          preventScroll: true
        });
      }
    };
    try {
      if (navigator.clipboard && window.isSecureContext) {
        try {
          await navigator.clipboard.writeText(prompt);
        } catch {
          copyWithSelection();
        }
      } else {
        copyWithSelection();
      }
      button.dataset.copyState = "copied";
      label.textContent = "Copied";
      status.textContent = "Prompt copied. Paste it into your coding agent.";
      button.dataset.copyResetTimer = String(window.setTimeout(() => {
        delete button.dataset.copyState;
        label.textContent = "Copy prompt";
        delete button.dataset.copyResetTimer;
      }, 2000));
    } catch {
      button.dataset.copyState = "error";
      label.textContent = "Try again";
      status.textContent = "Couldn't copy the prompt. Try again.";
    } finally {
      button.removeAttribute("aria-disabled");
    }
    try {
      window.posthog?.capture?.("docs_quickstart_prompt_copy_clicked", {
        quickstart: name.toLowerCase(),
        prompt_version: promptVersion,
        prompt_length: prompt.length,
        source_page: window.location.pathname
      });
    } catch {}
  };
  return <aside className="prebuilt-prompt" data-prompt-version={promptVersion} data-prompt-length={prompt.length} aria-label={children ? `${name} demo` : `${name} agent prompt`}>
      <div className="prebuilt-prompt-copy">
        <p className="prebuilt-prompt-title">{title}</p>
        <p className="prebuilt-prompt-description">
          {description}
        </p>
      </div>
      <button className="prebuilt-prompt-button" type="button" onClick={copyPrompt} data-attr="prebuilt-prompt-copy" aria-label={`Copy prompt for ${name}`}>
        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
          <g className="prebuilt-prompt-icon-copy">
            <rect x="9" y="9" width="13" height="13" rx="2" />
            <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
          </g>
          <path className="prebuilt-prompt-icon-check" d="m5 12 4 4L19 6" />
          <g className="prebuilt-prompt-icon-error">
            <circle cx="12" cy="12" r="9" />
            <path d="M12 8v4m0 4h.01" />
          </g>
        </svg>
        <span data-copy-label>Copy prompt</span>
      </button>
      {children}
      <span className="prebuilt-prompt-status" data-copy-status role="status" aria-live="polite" aria-atomic="true" />
    </aside>;
};

<PrebuiltPrompt name="ElevenLabs" title="Build this quickstart with a coding agent" description="Paste this prompt into your coding agent to set up and run the Next.js demo." prompt={elevenLabsAgentPrompt} promptVersion="elevenlabs-agent-prompt-v4">
  <a className="demo-source-link" href="https://github.com/anam-org/anam-cookbook/tree/main/examples/elevenlabs-server-side-agent-nextjs">
    <span aria-hidden="true">
      <Icon icon="github" iconType="brands" size={18} />
    </span>

    <span>View on GitHub</span>
  </a>
</PrebuiltPrompt>

## Overview

Anam's server-side ElevenLabs integration connects an Anam avatar to an ElevenLabs Conversational AI agent. Your server fetches a short-lived ElevenLabs signed URL and includes it when creating an Anam session token.

The browser uses the Anam JavaScript SDK to start the WebRTC session and render the avatar. Anam sends conversation audio to ElevenLabs, receives the generated speech, and keeps the avatar in sync. API keys stay on the server. Completed sessions appear in Anam Lab with a transcript and, when available, a [recording](/docs/personas/session/recordings). [Zero Data Retention](/docs/security/privacy) sessions store neither.

## Quickstart

Follow these three steps to give your ElevenLabs agent a face:

1. [Configure your ElevenLabs agent](/docs/integrations/elevenlabs/agent-configuration) for the audio format and voice settings.
2. [Set up the server](/docs/integrations/elevenlabs/server-side-setup) to fetch a signed URL and create an Anam session token.
3. [Start the avatar session](/docs/integrations/elevenlabs/start-session) in your browser.

### Upgrade your avatar

Build on your working session with [Director Notes](/docs/integrations/elevenlabs/director-notes) for expressive avatar performance, [session customisation](/docs/integrations/elevenlabs/session-customisation) for personalised conversations, and [client tools](/docs/integrations/elevenlabs/client-tools) for actions in your app. For connection and playback issues, see [Troubleshooting](/docs/integrations/elevenlabs/troubleshooting).

## How it works

The session is split between your Next.js server, the browser, and Anam's engine:

```
Server:
  1. Fetch signed URL from ElevenLabs API
  2. Create Anam session token with Cara 4 Director Notes
     and elevenLabsAgentSettings

Client:
  3. createClient(sessionToken)
  4. streamToVideoElement("avatar-video")

Engine (automatic):
  5. Connects to ElevenLabs agent via signed URL
  6. User speech → ElevenLabs STT → LLM → TTS → Anam face rendering
  7. Supported audio tags → Director Notes cues
  8. Avatar video delivered over WebRTC
  9. Client tool calls and results forwarded over the same connection
```

The Next.js route supplies the ElevenLabs settings when it creates the session token. The browser receives that token and uses the Anam JavaScript SDK to start the avatar stream.

## Supported features

These ElevenLabs agent features work through the server-side integration: voice intelligence (STT, LLM, TTS), expressive voices, mapped Director Notes cues, interruption handling, custom knowledge bases, server-side tools (webhooks), conversation history, and client tools.

The ElevenLabs connection and API keys remain server-side when client tools are enabled. Only the specific handler code runs in the browser.

<Card title="Step 1: Configure your agent" icon="arrow-right" href="/docs/integrations/elevenlabs/agent-configuration">
  Configure the ElevenLabs agent before adding the server route and browser client.
</Card>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.