Skip to main content
Use this page when you are building an app that creates and embeds Tavus CVI conversations. If you only want to create a PAL and start your first Tavus-hosted conversation, use API Conversation Quickstart.
This guide gets a new web app from an empty project to a working embedded Tavus conversation. The happy path is:
  1. Keep TAVUS_API_KEY on your server.
  2. Create a conversation with POST /v2/conversations.
  3. Embed the returned conversation_url in an iframe.
  4. End the conversation when the user leaves.
For API details, see Create Conversation, End Conversation, Get PALs, and Get Faces.
Live conversations can count toward billing and concurrency as soon as they are created. Use test_mode: true while wiring automated tests or checking your integration flow. In test mode, Tavus creates the conversation without the PAL joining and returns it with status: "ended".

Prerequisites

  • A Tavus API key from the PAL Maker.
  • A usable pal_id, face_id, or both. The next section shows how to choose.
  • Node.js 20+ for the TypeScript examples below.

Choose a PAL and Face

For CVI, a Face defines the on-screen likeness and voice. A PAL defines behavior, knowledge, and pipeline configuration (prompt, layers, objectives, guardrails, tools, and more). Use this decision tree:
  • If you have a PAL with a default_face_id, create the conversation with just pal_id.
  • If you have a PAL without a default_face_id, create the conversation with both pal_id and face_id.
  • If you only want to test a Face and voice without a custom PAL, create the conversation with just face_id.
First-time builders should start with stock Tavus resources. List stock PALs and stock faces with:
In the PALs response, look for pal_id and default_face_id. In the faces response, look for face_id, face_type, and model_name.
r90bbd427f71 is the stock Anna face ID used throughout these docs, and pcb7a34da5fe is a stock Sales Development Rep PAL ID. They are stock IDs, not placeholder strings, and are available for quickstart use.
For more details, see Stock Faces, Get PALs, Get Faces, and Create Conversation.

1. Create the app

Create a Vite React app and install the small server dependencies used in this guide:
Add these scripts to package.json:

2. Keep your API key server-only

Create .env.example:
Copy it to .env locally and fill in your real key:
Never expose TAVUS_API_KEY in browser code, client-side environment variables, mobile apps, or public repositories. The frontend should call your backend route, and your backend should call Tavus.

3. Add backend routes

Create server.ts at the project root. The first route creates a conversation. The second route ends it when the user leaves or your test finishes.

4. Embed the conversation URL

Replace src/App.tsx with this frontend. It calls your backend, receives the Tavus conversation_url, and embeds it in an iframe. This quickstart uses an iframe because it is the fastest path to a working CVI app. For Tavus-provided React components, including the complete CVIProvider + Conversation + server-helper example, see the @tavus/cvi-ui component library. For Daily JS/React or LiveKit guidance, see Embed CVI.
Run both servers:
Open http://localhost:5173, enter your pal_id, and click Start conversation.
The iframe must include browser permissions in the allow attribute. At minimum, include camera and microphone. fullscreen, display-capture, and autoplay are recommended for the default Tavus/Daily in-call experience.

Expected create response

POST /v2/conversations returns the join URL your app should embed:
When test_mode is true, expect the same shape, but status is ended and the PAL does not join.

Cleanup

End live conversations when the user leaves, when a test completes, or when your app no longer needs the room:
The sample app calls the same endpoint through your backend route:

Errors and cleanup

For automated tests, scaffolding, and agent-generated validation, create conversations with test_mode: true. the PAL does not join, the response returns status: "ended", and the conversation does not affect billing or concurrency. For live conversations, call End Conversation when the user leaves or your app no longer needs the room. Use Delete Conversation only when you want destructive data removal, not routine call cleanup. If conversation creation fails:
  • 400 usually means the request body is invalid. Check that you sent a valid pal_id, face_id, or both, and that customizations are in the expected location.
  • 401 means the Tavus API key is missing or invalid. Keep TAVUS_API_KEY on the server and never send it from browser code.
  • Quota or concurrency errors mean your app should stop creating live conversations, surface a retry/support path, and use test_mode: true for validation flows.
  • For private rooms, join with the returned meeting_token. If a token is invalid or expired, create a new authenticated conversation instead of reusing the old token.

Where to go next

  • Use the React component library when you want Tavus CVI components instead of a plain iframe.
  • Use Embed CVI for iframe, vanilla JavaScript, and Daily JS patterns.
  • Use customize the conversation UI for Daily Prebuilt styling.
  • Use LiveKit Agent only if you already run a LiveKit Agents pipeline and want Tavus as the avatar video layer. It is not the recommended path for most CVI apps because LiveKit only provides rendering, while Tavus’s Full Pipeline includes perception, turn-taking, and rendering for complete conversational intelligence.
  • Use conversation customizations for recording, language, participant limits, private rooms, backgrounds, captions, and timeouts.
  • Point coding agents at Agents & automation for llms.txt, OpenAPI, Agent Skills, and MCP access.