Developer quickstart

Add a MeshLite avatar to your site

The avatar runs in an iframe served by MeshLite and is rendered in your visitor’s browser. Your page loads one script, your server issues short-lived session tokens, and voice and AI come from your own provider account.

No code to write? Create a project

In the Console, open Projects, click New project, enter your site’s address and choose an avatar. The project shows a code to paste once before </body>, or a prompt for your AI assistant (ChatGPT, Claude, Cursor, Lovable, Bolt). The code has no secret key and needs no server or API key. It works only on your site, with its www address. The Console shows where to paste it in WordPress, Shopify, Wix, Webflow and Squarespace. The project changes to Online when a page of your site shows the avatar. Add or remove avatars later without changing the code.

The rest of this page is the developer integration: your own server, your own buttons and full control of the session. Building a voice agent with LiveKit or Pipecat? Go straight to the LiveKit guide or the Pipecat guide.

Using an AI coding agent?

Paste this prompt into Claude Code, Codex, Cursor or another coding agent. It follows our integration guide for agents, writes the server route and the page code, and tells you what to set up in the Console. Your secret key stays out of the chat: you add it to your .env file yourself.

Add a MeshLite talking avatar to this website. Follow the guide at https://meshlite.io/llms.txt (if you can't open it, ask me to paste it). Ask me for my MeshLite publishable key and, optionally, an avatar ID. Never ask me to paste my secret key: put a placeholder in .env and I'll add it myself. If I don't have a MeshLite account, send me to https://console.meshlite.io/console?plan=free#signup first.
Get API key

Agent without web access? Copy the full guide and paste it into the conversation.

Create your workspace

Sign up in the Console: free, no card, activated from the email we send. The Free plan can go live on one site (a domain with its www address and subdomains), with the default avatars, the MeshLite watermark on the avatar and up to 3 simultaneous conversations; the avatar speaks through your own voice provider. Removing the watermark needs a paid plan.

Set up the Console

The developer settings are in Projects, under For developers (tabs Keys, Allowed sites and Code). Voice services and personalities have their own menus.

Then add them to your server’s environment. The secret key never goes in browser code or Git.

MESHLITE_SECRET_KEY=ml_sk_test_...
MESHLITE_PUBLISHABLE_KEY=ml_pk_test_...
MESHLITE_ORIGIN=http://localhost:3000
MESHLITE_AVATAR=

Issue session tokens from your server

Your server keeps the secret key and asks MeshLite for a session token. Call this from a POST /api/meshlite-session route and return { sessionToken }. Tokens last 2 minutes; the SDK asks for a new one when it needs it. Node.js 18+, no dependency:

export async function createMeshLiteSession() {
  const response = await fetch("https://console.meshlite.io/api/v1/client-sessions", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.MESHLITE_SECRET_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      publishableKey: process.env.MESHLITE_PUBLISHABLE_KEY,
      origin: process.env.MESHLITE_ORIGIN, // from your config, never from the request
      avatar: process.env.MESHLITE_AVATAR || undefined,
      conversation: { language: "en", responseStyle: "concise", idleTimeout: 30 },
    }),
  });
  const body = await response.json();
  if (!response.ok) throw new Error(`MeshLite ${body.error?.code}: ${body.error?.message}`);
  return body.sessionToken;
}

The route is public: rate-limit it, or put it behind your own sign-in if only signed-in users should talk to the avatar.

Mount the avatar in your page

The iframe takes the size of its container. Browsers only open the microphone after a click, so start the conversation from one.

<div id="avatar" style="width:340px;height:510px"></div>
<button id="talk">Talk to the avatar</button>
<script src="https://console.meshlite.io/sdk/1.11.52/meshlite.js" crossorigin="anonymous"></script>
<script>
  const avatar = MeshLite.mount({
    container: "#avatar",
    avatar: "YOUR_AVATAR_ID",          // same as MESHLITE_AVATAR; remove for the default avatar
    locale: "en",
    publishableKey: "ml_pk_test_...",  // safe to expose
    sessionToken: async () => {
      const response = await fetch("/api/meshlite-session", { method: "POST" });
      if (!response.ok) throw new Error("Could not start the avatar session");
      return (await response.json()).sessionToken;
    },
    conversation: { language: "en", responseStyle: "concise", idleTimeout: 30 },
    captions: "overlay",
  });
  avatar.on("error", (message, detail) => console.error("MeshLite", detail.code, message));
  document.getElementById("talk").addEventListener("click", () => avatar.startConversation());
</script>

React, Next.js, Vue: load the script once, mount in a client-side effect (useEffect, onMounted) and call avatar.unmount() in its cleanup. There is no npm package: load the SDK from console.meshlite.io only.

Going live

In Projects, under For developers, open Allowed sites and add your production domain as a live domain (HTTPS), create live keys and replace the test values in your environment. No code change is needed. The Free plan goes live on one site with the MeshLite watermark; paid plans add custom avatars, and from Creator up they also remove the watermark and allow more simultaneous conversations (Lite keeps a MeshLite attribution).

Guide: a LiveKit voice agent with a MeshLite face

You will run a complete demo on your computer: a voice agent built with LiveKit Agents talks to a visitor in a web page, and a MeshLite avatar moves its lips in sync. Your agent keeps its own brain and voice (speech-to-text, language model, voice, or a realtime model). MeshLite only draws the face, in the visitor’s browser. Count about 10 minutes.

How it works: the agent sends its voice to the page over the LiveKit data channel, and the MeshLite SDK in the page plays it in sync with the face. There is no video server, no GPU and no extra participant in the room. No MeshLite voice minutes are used. The session still counts as a conversation (simultaneous conversations and statistics) and lasts at most 2 hours.

What you need

1. Prepare the Console

In Projects, under For developers:

2. Get the example

git clone https://github.com/meshlite-avatar/livekit-plugins-meshlite
cd livekit-plugins-meshlite/examples/browser-avatar
cp .env.example .env     # Windows: copy .env.example .env

Open .env and fill in the settings:

3. Start everything with one command

python run.py

The launcher works on Windows, macOS and Linux. It creates a virtual environment and installs the dependencies (first run only, one or two minutes), checks .env and names anything missing without ever printing a value, starts the agent and the web server with prefixed logs, waits for the agent to connect to LiveKit, opens the page, and stops everything on Ctrl+C. You should see:

[run] Waiting for the agent to connect to LiveKit ...
[agent] registered worker
[run] The agent is connected to LiveKit and waits for a visitor.
[run] Ready: http://localhost:8080
[run] Press Start in the page and allow the microphone. Press Ctrl+C here to stop everything.

If something is missing, run python run.py --check: it lists each missing or wrong setting and exits.

4. Talk to your avatar

In the page, press Start and allow the microphone. The status goes from “Connecting…” to “Avatar ready”, then the face greets you and answers with matching lips. Interrupt it by speaking: it stops at once, and the agent keeps only what you actually heard. On Chrome, Edge and Safari the voice plays in the avatar frame; on Firefox the page plays the voice itself and the avatar moves its lips in silence, so that Firefox’s echo cancellation can hear it.

5. Use it in your own agent and page

Everything the example does comes down to three pieces. On the agent side (Python, livekit-agents 1.x):

pip install livekit-plugins-meshlite
from livekit.agents import Agent, AgentSession, JobContext
from livekit.plugins import meshlite

async def entrypoint(ctx: JobContext):
    await ctx.connect()
    session = AgentSession(
        # stt=..., llm=..., tts=...  or a realtime model
        resume_false_interruption=False,
    )
    avatar = meshlite.AvatarSession()
    await avatar.start(session, room=ctx.room)   # after ctx.connect(), before session.start()
    await session.start(agent=Agent(instructions="..."), room=ctx.room)

The agent then publishes no audio track: its voice reaches the page as data. If your agent version offers false-interruption resume, keep it off, as above: a speech segment cannot be paused and resumed.

On your server, create the MeshLite session as in Issue session tokens from your server, with audio: "livekit" added to the body, and create the visitor’s LiveKit participant token in the same route. The secret key stays on the server:

body: JSON.stringify({
  publishableKey: process.env.MESHLITE_PUBLISHABLE_KEY,
  origin: process.env.MESHLITE_ORIGIN,   // from your config, never from the request
  avatar: process.env.MESHLITE_AVATAR,
  audio: "livekit",
}),

In the page, mount the avatar with audio: "livekit" and attach the room before connecting, so no speech is missed. Do not subscribe to the agent’s audio yourself: the SDK plays the voice. This needs livekit-client 2.9 or later and the MeshLite SDK 1.11.45 or later:

<div id="avatar" style="width:340px;height:510px"></div>
<button id="start">Start</button>
<script src="https://console.meshlite.io/sdk/1.11.52/meshlite.js" crossorigin="anonymous"></script>
<script type="module">
  import { Room } from "https://cdn.jsdelivr.net/npm/livekit-client@2.23.0/+esm";

  document.getElementById("start").addEventListener("click", async () => {
    // Your server returns sessionToken, livekitUrl and participantToken.
    const s = await (await fetch("/api/session", { method: "POST" })).json();

    const avatar = MeshLite.mount({
      container: "#avatar",
      avatar: s.avatar,
      publishableKey: s.publishableKey,
      sessionToken: s.sessionToken,
      audio: "livekit",
    });

    const room = new Room();
    const link = avatar.attachLiveKit(room);          // before room.connect
    link.on("ready", ({ output }) => console.log("avatar ready, voice played by", output));
    link.on("fallback", ({ code }) => console.warn("avatar unavailable, the voice continues", code));
    link.on("audio-blocked", () => showEnableSoundButton(() => link.resumeAudio()));

    await room.connect(s.livekitUrl, s.participantToken);
    await room.localParticipant.setMicrophoneEnabled(true);
  });
</script>

The avatar never blocks the conversation. If the face cannot start or stops (refused session, network, maximum duration), the page plays the agent’s voice itself from the position already heard, and link emits fallback with an error code. Pass fallback: false to attachLiveKit if you prefer it to stop. The plugin repository lists every option.

6. Go to production

If something does not work

Guide: a Pipecat bot with a MeshLite face

You will run a complete demo on your computer: a voice bot built with Pipecat talks to a visitor in a web page, and a MeshLite avatar moves its lips in sync. Your bot keeps its own brain, voice and transport (speech-to-text, language model and voice, or a realtime model). MeshLite only draws the face, in the visitor’s browser. Count about 10 minutes.

How it works: the page already receives the bot’s voice as a WebRTC audio track. It hands that track to the MeshLite SDK, which animates the face in the browser and follows Pipecat’s speaking and interruption events. Nothing changes in the bot: no plugin, no extra participant, no video server and no GPU. No MeshLite voice minutes are used. The session still counts as a conversation (simultaneous conversations and statistics) and lasts at most 2 hours.

What you need

1. Prepare the Console

In Projects, under For developers:

2. Get the example

git clone https://github.com/meshlite-avatar/livekit-plugins-meshlite
cd livekit-plugins-meshlite/examples/pipecat-browser-avatar
cp .env.example .env     # Windows: copy .env.example .env

The example sits in the LiveKit plugin repository but uses no LiveKit. Open .env and fill in the settings:

3. Start it with one command

python run.py

The launcher works on Windows, macOS and Linux. It creates a virtual environment and installs the dependencies (first run only, a few minutes), checks .env and names anything missing without ever printing a value, starts the bot with prefixed logs, opens the page, and stops everything on Ctrl+C. You should see:

[run] Starting the bot (the first start takes a few seconds) ...
[run] Ready: http://localhost:8080
[run] Press Start in the page and allow the microphone. Press Ctrl+C here to stop.
[bot] INFO    Visitor connected: the bot greets them.

If something is missing, run python run.py --check: it lists each missing or wrong setting and exits.

4. Talk to your avatar

In the page, press Start and allow the microphone. The status goes from “Connecting…” to “Avatar ready”, then the face greets you and answers with matching lips. Interrupt it by speaking: it stops at once, and the rest of the cut answer is not replayed. On Chrome, Edge and Safari the voice plays in the avatar frame; on Firefox the page plays the voice itself and the avatar moves its lips in silence, so that Firefox’s echo cancellation can hear it.

The face starts speaking about 0.6 seconds after the voice reaches the page: a live audio track gives no “segment ready” signal, so the avatar needs a little audio before its lips can move. Until the face is ready, the page plays the voice itself, so the visitor never waits for the face.

5. Use it in your own bot and page

The bot needs no change: keep your Pipecat pipeline and transport, with RTVI on (the default). On your server, create the MeshLite session as in Issue session tokens from your server, with audio: "track" added to the body. The secret key stays on the server:

body: JSON.stringify({
  publishableKey: process.env.MESHLITE_PUBLISHABLE_KEY,
  origin: process.env.MESHLITE_ORIGIN,   // from your config, never from the request
  avatar: process.env.MESHLITE_AVATAR,
  audio: "track",
}),

In the page, mount the avatar with audio: "track" and attach the Pipecat client before connecting, so no speech is missed. Keep your usual bot audio element: the SDK mutes it while the face plays the voice and unmutes it whenever the face cannot, so the visitor hears the bot exactly once. This needs @pipecat-ai/client-js 1.x and the MeshLite SDK 1.11.48 or later, and works with SmallWebRTC, Daily or any other Pipecat transport:

<div id="avatar" style="width:340px;height:510px"></div>
<button id="start">Start</button>
<audio id="bot-audio" autoplay></audio>
<script src="https://console.meshlite.io/sdk/1.11.52/meshlite.js" crossorigin="anonymous"></script>
<script type="module">
  import { PipecatClient } from "https://cdn.jsdelivr.net/npm/@pipecat-ai/client-js@1.16.0/+esm";
  import { SmallWebRTCTransport } from "https://cdn.jsdelivr.net/npm/@pipecat-ai/small-webrtc-transport@1.11.2/+esm";

  document.getElementById("start").addEventListener("click", async () => {
    // Your server returns sessionToken, publishableKey and avatar.
    const s = await (await fetch("/api/session", { method: "POST" })).json();

    const avatar = MeshLite.mount({
      container: "#avatar",
      avatar: s.avatar,
      publishableKey: s.publishableKey,
      sessionToken: s.sessionToken,
      audio: "track",
    });

    const client = new PipecatClient({
      transport: new SmallWebRTCTransport(),
      enableMic: true,
      callbacks: {
        onTrackStarted: (track, participant) => {   // your usual bot audio element
          if (track.kind === "audio" && !participant?.local) {
            document.getElementById("bot-audio").srcObject = new MediaStream([track]);
          }
        },
      },
    });
    const link = avatar.attachPipecat(client);        // before client.connect
    link.on("ready", ({ output }) => console.log("avatar ready, voice played by", output));
    link.on("fallback", ({ code }) => console.warn("avatar unavailable, the voice continues", code));
    link.on("audio-blocked", () => showEnableSoundButton(() => link.resumeAudio()));

    await client.connect({ webrtcUrl: "/api/offer" });   // your usual Pipecat connection
  });
</script>

The avatar never blocks the conversation. If the face cannot start or stops (refused session, network, maximum duration), your page keeps playing the bot’s voice and link emits fallback with an error code. Another WebRTC voice agent without Pipecat? Mount with audio: "track" in the same way and call avatar.attachAudioTrack(track) with the agent’s audio track; the full guide lists its options and events. The example has the complete code.

6. Go to production

If something does not work

Reference

Questions about your integration? Write to hello@meshlite.io.