Skip to main content
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.
ComponentWhat the PAL showsDefault slot
questionA multiple-choice question, optionally with a free-text “Other”safe-area-right
inputA prompt for a single typed valuesafe-area-right
calendarA date, time-slot, or date-range pickersafe-area-right
textA card of formatted textsafe-area-right
chartA bar, line, or pie chartsafe-area-right
alertA dismissible noticesafe-area-right
scheduling_embedYour real scheduling page (e.g. Calendly), embedded for live bookingsafe-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

FieldTypeRequiredDescription
enabledbooleanAccepted by every component. Defaults to true. Set false to disable the component.
providerstringscheduling_embed only. Defaults to "calendly", the only supported provider.
scheduling_urlstringscheduling_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 & pathWhat it does
GET /v2/pals/{pal_id}/skillsList every skill attached to the PAL.
PUT /v2/pals/{pal_id}/skillsReplace 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": "..." }:
StatusWhenError message contains
404PUT or PATCH with a skill id that isn’t registered (misspelled magic_canvas)Unknown skill '<skill_id>'
404GET, PATCH, or DELETE on a skill that isn’t attached to the PALSkill '<skill_id>' is not attached to this PAL
400Unknown key anywhere inside config (misspelled component, stray field)the offending key and Unknown field.
400scheduling_url doesn’t start with https://scheduling_url must use HTTPS
400scheduling_url is longer than 2048 charactersscheduling_url must be at most 2048 characters
400scheduling_url has no hostnamescheduling_url must include a hostname
400scheduling_url host is localhost or a cloud metadata hostnamescheduling_url host '<host>' is not allowed
400scheduling_url host is an internal IP addressscheduling_url IP '<host>' is not allowed (internal address space)
400The pal_id in the URL doesn’t existBad 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.