Skip to main content
Magic Canvas lets a PAL show interactive cards, such as questions, calendars, and charts, during a conversation. The PAL’s LLM decides when to show one; user input flows back to the conversation and your webhook.
1

Attach the Magic Canvas skill

Attaching the skill enables the available components with default settings:
curl -X PUT https://tavusapi.com/v2/pals/{pal_id}/skills/magic_canvas \
  -H "Content-Type: application/json" \
  -H "x-api-key: <your-api-key>" \
  -d '{ "config": {} }'
config.components is a sparse overlay, not an allowlist; add an entry only to configure or disable a component:
Disable charts
curl -X PUT https://tavusapi.com/v2/pals/{pal_id}/skills/magic_canvas \
  -H "Content-Type: application/json" \
  -H "x-api-key: <your-api-key>" \
  -d '{ "config": { "components": { "chart": { "enabled": false } } } }'
Later components are enabled automatically on PALs with the skill attached; disable them the same way. See Configuring your PAL for all component settings.
The skill has no effect on echo and speech-to-speech PALs.
2

Create the conversation

Every video conversation with this PAL gets Canvas. Set callback_url; interactions arrive there in step 4:
curl -X POST https://tavusapi.com/v2/conversations \
  -H "Content-Type: application/json" \
  -H "x-api-key: <your-api-key>" \
  -d '{
    "face_id": "r79e1c033f",
    "pal_id": "p5317866",
    "callback_url": "https://yourapp.example.com/webhooks/tavus"
  }'
Audio-only, text-chat, and Zoom/Teams/Meet (meeting_url) conversations never receive Canvas actions.
Conversation creation uses your API key; call it from your backend, never the browser.
3

Render the canvas

With the Tavus-hosted embed or widget, Canvas renders automatically:
<script src="https://unpkg.com/@tavus/embed"></script>
<tavus-embed deployment-id="YOUR_DEPLOYMENT_ID"></tavus-embed>
For React, add the @tavus/cvi-ui component:
npx @tavus/cvi-ui@latest add magic-canvas
Mount it inside the same CVIProvider as your conversation UI:
import { CVIProvider } from "./components/cvi/components/cvi-provider";
import { Conversation } from "./components/cvi/components/conversation";
import { MagicCanvas } from "./components/cvi/components/magic-canvas";

<CVIProvider>
  <Conversation conversationUrl={conversationUrl} />
  <MagicCanvas
    onInteraction={(event) => console.log("user interacted:", event)}
  />
</CVIProvider>
The default class sets position: fixed (full-viewport overlay). To keep cards inside your player, wrap both in a position: relative container and pass a className that sets position: absolute !important.
4

Receive interactions

Each interaction arrives at your callback_url as a canvas.interaction event, fired once when first recorded; duplicate submissions and client retries never re-fire it:
canvas.interaction
{
  "properties": {
    "conversation_id": "c123456",
    "interaction_id": "ci_call_abc123_submit_8f3d2a",
    "tool_call_id": "call_abc123",
    "component": "canvas.question",
    "component_version": "v1",
    "type": "submit",
    "value": { "selected_option_ids": ["opt_2"], "skipped": false },
    "metadata": {},
    "created_at": "2026-06-09T21:14:03.412210"
  },
  "conversation_id": "c123456",
  "message_type": "canvas",
  "event_type": "canvas.interaction",
  "timestamp": "2026-06-09T21:14:03.498Z"
}
Fetch the full history any time with your API key:
curl https://tavusapi.com/v2/conversations/{conversation_id}/canvas/interactions \
  -H "x-api-key: <your-api-key>"
The response is { "data": [ ... ] }, oldest first, with the same fields as the webhook’s properties.

Components

Attaching the skill enables the components below with default settings. scheduling_embed needs a booking link (provider plus scheduling_url) configured before it activates.
ComponentWhat the PAL can do with it
questionAsk a multiple-choice question, optionally with a free-text “Other”
inputAsk for a single typed value (text, email, number, or phone)
calendarLet the user pick a date, a time slot, or a date range
scheduling_embedEmbed your real scheduling page (e.g. Calendly) for live booking
textShow a card of formatted text
chartShow a bar, line, or pie chart
alertShow a dismissible notice
scheduling_embed activates only once you configure a booking link (a public HTTPS URL); unconfigured, it stays inactive and produces no error:
"config": {
  "components": {
    "scheduling_embed": {
      "provider": "calendly",
      "scheduling_url": "https://calendly.com/your-team/30min"
    }
  }
}
Cards render in a sandboxed iframe on Tavus infrastructure that isolates styles and scripts in both directions. Your webhook receives skip and dismiss interactions in addition to submit.