To configure Magic Canvas, attach the magic_canvas skill to the PAL. The skill controls which Canvas components the PAL may use; the PAL decides at runtime when to show one.
Attaching the skill enables every component with defaults; there is no per-component opt-in. Audio-only, chat, and external-meeting conversations never get Canvas actions; see how the configuration reaches a conversation.
Attaching the Skill
Attach with a single PUT:
curl -X PUT https://tavusapi.com/v2/pals/{pal_id}/skills/magic_canvas \
-H "x-api-key: <your-api-key>" \
-H "Content-Type: application/json" \
-d '{ "config": {} }'
{
"skill_id": "magic_canvas",
"config": {},
"attached_at": "2026-06-10T17:04:52.183947+00:00",
"updated_at": "2026-06-10T17:04:52.183947+00:00"
}
Common configurations, shown as the PAL’s skills value:
// Everything on, defaults (scheduling_embed inactive: needs config)
"skills": { "magic_canvas": { "config": {} } }
// Everything on + the scheduling embed configured
"skills": { "magic_canvas": { "config": {
"components": { "scheduling_embed": { "provider": "calendly", "scheduling_url": "https://calendly.com/x/30min" } }
} } }
// Everything except chart
"skills": { "magic_canvas": { "config": {
"components": { "chart": { "enabled": false } }
} } }
Component Overlay
config.components is a sparse overlay, not an allowlist. Components you don’t mention stay enabled with defaults. Add an entry to:
- Configure a component: for example, set
scheduling_url on scheduling_embed.
- Disable a component: set
{ "enabled": false }.
New components Tavus ships later are enabled automatically on PALs with the skill attached. Disable them per component to opt out.
scheduling_embed only activates once scheduling_url is set. Attaching the
skill without that config doesn’t error; the component stays inactive.
config is strictly validated: an unknown component name or stray field returns 400. A misspelled skill id in the URL returns 404: Unknown skill '...' on PUT and PATCH, the not-attached 404 on GET and DELETE.
Components
Each active component gives the PAL one action named canvas_show_<component> (for example, canvas_show_question). When at least one component is active, the PAL also gets canvas_clear and update_component to dismiss or revise cards it already showed.
| Component | What the PAL shows | Default slot |
|---|
question | A multiple-choice question, optionally with a free-text “Other” | safe-area-right |
input | A prompt for a single typed value | safe-area-right |
calendar | A date, time-slot, or date-range picker | safe-area-right |
text | A card of formatted text | safe-area-right |
chart | A bar, line, or pie chart | safe-area-right |
alert | A dismissible notice | safe-area-right |
scheduling_embed | Your real scheduling page (e.g. Calendly), embedded for live booking | safe-area-right |
Every card renders inline in a side rail next to the PAL video. The only placements offered are safe-area-right (the default) and safe-area-left; the PAL can pick the other side at render time. Components whose catalog default is a different slot are clamped to an inline side rail when the card renders, so the effective placement is always one of the two side rails. Only one card is on screen at a time: showing or updating a card replaces whatever is currently displayed.
Component Fields
| Field | Type | Required | Description |
|---|
enabled | boolean | ❌ | Accepted by every component. Defaults to true. Set false to disable the component. |
provider | string | ❌ | scheduling_embed only. Defaults to "calendly", the only supported provider. |
scheduling_url | string | ❌ | scheduling_embed only. Your booking link, delivered to the embed exactly as configured. Defaults to "" (component inactive). |
scheduling_url Rules
A non-empty scheduling_url must be a public HTTPS URL:
- Must start with
https:// and include a hostname.
- At most 2048 characters.
localhost and cloud metadata hostnames (such as metadata.google.internal) are rejected.
- IP-based hosts (including hex and other numeric encodings) in private, loopback, link-local, or otherwise internal address space are rejected.
- An empty string is allowed and means “not configured yet.”
Skills API
Skill attachments live under /v2/pals/{pal_id}/skills. Full HTTP reference:
Endpoints:
| Method & path | What it does |
|---|
GET /v2/pals/{pal_id}/skills | List every skill attached to the PAL. |
PUT /v2/pals/{pal_id}/skills | Replace the PAL’s entire skill set in one request. |
GET /v2/pals/{pal_id}/skills/{skill_id} | Read one attachment. |
PUT /v2/pals/{pal_id}/skills/{skill_id} | Attach a skill, or overwrite its config if already attached. |
PATCH /v2/pals/{pal_id}/skills/{skill_id} | Merge changes into an existing attachment’s config. |
DELETE /v2/pals/{pal_id}/skills/{skill_id} | Detach a skill. |
You can read skills on stock PALs, but you can only modify skills on PALs you own.
Reading Configuration
The single-skill GET returns the attachment as stored:
{
"skill_id": "magic_canvas",
"config": {
"components": { "chart": { "enabled": false } }
},
"attached_at": "2026-06-10T17:04:52.183947+00:00",
"updated_at": "2026-06-11T09:31:08.412605+00:00"
}
The per-skill PUT, PATCH, and GET return the attachment object directly. The list GET and the bulk PUT wrap attachments in a data map keyed by skill id:
{
"data": {
"magic_canvas": {
"skill_id": "magic_canvas",
"config": {},
"attached_at": "2026-06-10T17:04:52.183947+00:00",
"updated_at": "2026-06-10T17:04:52.183947+00:00"
}
}
}
Updating with PATCH
PATCH requires a { "config": ... } body (unlike PUT, where it defaults to {}), merges it into the existing config, and returns 404 if the skill isn’t attached:
curl -X PATCH https://tavusapi.com/v2/pals/{pal_id}/skills/magic_canvas \
-H "x-api-key: <your-api-key>" \
-H "Content-Type: application/json" \
-d '{ "config": { "components": { "chart": { "enabled": false } } } }'
Merge rules:
- The merge is shallow, at the top level of
config: a PATCH that includes components replaces the whole overlay map rather than deep-merging per component. Send the complete set of overrides you want to end up with.
- Setting a top-level config key to
null removes it: { "config": { "components": null } } clears every override and returns the skill to defaults.
The merged result is re-validated in full.
Replacing All Skills
PUT /v2/pals/{pal_id}/skills takes { "skills": { ... } } and replaces the PAL’s entire skill set:
curl -X PUT https://tavusapi.com/v2/pals/{pal_id}/skills \
-H "x-api-key: <your-api-key>" \
-H "Content-Type: application/json" \
-d '{ "skills": { "magic_canvas": { "config": {} } } }'
The bulk PUT is a full replace: any skill missing from the payload is detached.
To change only Canvas, use the per-skill PUT or PATCH instead.
Skills that were already attached keep their original attached_at.
Detaching
curl -X DELETE https://tavusapi.com/v2/pals/{pal_id}/skills/magic_canvas \
-H "x-api-key: <your-api-key>"
Returns 204 with no body. Detaching removes Canvas actions from all future conversations and deletes the attachment’s config; re-attaching starts from the config you send next.
Validation Errors
Errors return { "error": "..." }:
| Status | When | Error message contains |
|---|
404 | PUT or PATCH with a skill id that isn’t registered (misspelled magic_canvas) | Unknown skill '<skill_id>' |
404 | GET, PATCH, or DELETE on a skill that isn’t attached to the PAL | Skill '<skill_id>' is not attached to this PAL |
400 | Unknown key anywhere inside config (misspelled component, stray field) | the offending key and Unknown field. |
400 | scheduling_url doesn’t start with https:// | scheduling_url must use HTTPS |
400 | scheduling_url is longer than 2048 characters | scheduling_url must be at most 2048 characters |
400 | scheduling_url has no hostname | scheduling_url must include a hostname |
400 | scheduling_url host is localhost or a cloud metadata hostname | scheduling_url host '<host>' is not allowed |
400 | scheduling_url host is an internal IP address | scheduling_url IP '<host>' is not allowed (internal address space) |
400 | The pal_id in the URL doesn’t exist | Bad Request. PAL not found |
How the configuration reaches a conversation
At conversation create, Tavus resolves the PAL’s Canvas action list once:
- Audio-only (
audio_only: true), chat, and external-meeting (meeting_url: Zoom, Teams, Meet) conversations never get Canvas actions.
- Every other conversation with the skill attached gets one
canvas_show_<component> action per active component.
canvas_clear and update_component are added once at least one component is active.
- Canvas actions never overwrite a tool you defined with the same name; your tool wins.