> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tavus.io/llms.txt
> Use this file to discover all available pages before exploring further.

# PAL Canvas Configuration

> Full reference for the magic_canvas skill on PALs: attaching it, the component overlay, the skills API, and every validation error.

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](#how-the-configuration-reaches-a-conversation).

## Attaching the Skill

Attach with a single `PUT`:

```bash theme={null}
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": {} }'
```

```json theme={null}
{
  "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:

```json theme={null}
// Everything on, defaults (scheduling_embed inactive: needs config)
"skills": { "magic_canvas": { "config": {} } }
```

```json theme={null}
// Everything on + the scheduling embed configured
"skills": { "magic_canvas": { "config": {
  "components": { "scheduling_embed": { "provider": "calendly", "scheduling_url": "https://calendly.com/x/30min" } }
} } }
```

```json theme={null}
// Everything except chart
"skills": { "magic_canvas": { "config": {
  "components": { "chart": { "enabled": false } }
} } }
```

```json theme={null}
// Everything on + two Knowledge Base images the PAL may show
"skills": { "magic_canvas": { "config": {
  "components": { "image": { "images": [
    { "document_id": "d1234567890", "caption": "Aurora Desk in walnut", "prompt": "Show when the user asks about finishes." },
    { "document_id": "d2468101214", "caption": "Cable tray, underside detail" }
  ] } }
} } }
```

```json theme={null}
// Everything on + web images from allowlisted sites
"skills": { "magic_canvas": { "config": {
  "components": { "image": { "web": {
    "enabled": true,
    "domains": ["photos.example.com", "*.cdn.example.com"],
    "guidance": "Show the listing photo whenever search_listings returns one."
  } } }
} } }
```

```json theme={null}
// Everything on + guidance on when to show cards
"skills": { "magic_canvas": { "config": {
  "usage_guidance": "Show a chart whenever you compare numbers; ask with a question card before booking."
} } }
```

## 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 adds later are enabled automatically on PALs with the skill attached. Disable them per component to opt out.

<Note>
  Two components need config before they do anything. `scheduling_embed` only
  activates once `scheduling_url` is set, and `image` only activates once you
  curate at least one Knowledge Base image or enable its `web` allowlist.
  Attaching the skill without that config doesn't error; the component stays
  inactive.
</Note>

`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`.

## When cards appear

There is no API to show a specific card. The PAL chooses when to call a Canvas action during the conversation. You influence that in three places:

| Lever | Where | Best for |
| - | - | - |
| `usage_guidance` | `config.usage_guidance` on the `magic_canvas` skill (the **When to use Magic Canvas** field in the builder) | Canvas-specific rules, for example "show a chart when you compare numbers" or "ask with a question card before booking" |
| System prompt | The PAL's main prompt | Persona, conversation flow, and broader guidance on when UI helps |
| Conversational context | `conversational_context` at conversation create | Session-specific facts (availability, user details) the PAL can use when picking a card |

Tavus appends `usage_guidance` to the Canvas system prompt. It is ignored when blank or when no component is active.

Start by disabling components the PAL should not use (`config.components.<name>.enabled: false`). An enabled component can still appear even if your prompts never mention it.

```json theme={null}
{
  "config": {
    "usage_guidance": "Show a chart whenever you compare numbers; ask with a question card before booking."
  }
}
```

## 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`. See the [components overview](/sections/conversational-video-interface/magic-canvas/components) for descriptions and per-component reference.

Every card renders inline in the `safe-area-right` side rail next to the PAL video. Placement is client-side; the PAL does not choose the side. Only one card is on screen at a time: showing 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). |
| `images` | array | ❌ | `image` only. The Knowledge Base images the PAL may show, at most 50. Defaults to `[]` (component inactive). See [`images` rules](#images-rules). |
| `web` | object | ❌ | `image` only. Lets the PAL show images from URLs it received mid-conversation, restricted to websites you allowlist. Defaults to disabled. See [`web` rules](#web-rules). |

### `images` Rules

Each entry is `{ "document_id", "caption", "prompt" }`:

* `document_id` (required) is a [Knowledge Base](/sections/conversational-video-interface/knowledge-base) document id. (Once an image is uploaded to the Knowledge Base, it receives a `document_id`.) It must be accessible to the account that owns the PAL and must have finished processing, both checked when you save the config.
* `caption` (optional, max 280 characters) is shown under the image. Blank falls back to the description Tavus generated for the image, then the document name.
* `prompt` (optional, max 500 characters) tells the PAL when this image applies. It is never shown to the user.

The PAL selects a curated image by `document_id`; the URL it renders from is resolved by Tavus, never supplied by the model. See the [`image` component](/sections/conversational-video-interface/magic-canvas/components/image) for the full flow.

### `web` Rules

`image.web` is `{ "enabled", "domains", "guidance" }` and adds a second image source: URLs the PAL received during the conversation (typically from a tool result), restricted to websites you allowlist.

* `enabled` (optional) defaults to `false` and requires at least one entry in `domains` to turn on. `false` keeps a saved list without deleting it.
* `domains` (optional, max 20 entries) lists the allowed websites. Each entry is an exact host (`photos.example.com`) or a leading wildcard (`*.cdn.example.com`, matching any depth of subdomain, never the apex). Entries are hostname-only, max 253 characters, punycode for international names, and are normalized on save (lowercased, trailing dot stripped, deduplicated). IP addresses, local and internal names, and wildcards over shared suffixes (`*.github.io`, `*.co.uk`) are rejected.
* `guidance` (optional, max 500 characters) tells the PAL when to show a web image. Never shown to the user.

With a live allowlist, the compiled `canvas_show_image` action additionally accepts `images[].url`; every URL is checked at render time against a fixed safety floor (https, public hostname) and then your allowlist. See [how a URL becomes an image](/sections/conversational-video-interface/magic-canvas/components/image#how-a-url-becomes-an-image).

### `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:

* [List PAL skills](/api-reference/pal-skills/list-pal-skills)
* [Get PAL skill](/api-reference/pal-skills/get-pal-skill)
* [Attach skill to PAL](/api-reference/pal-skills/attach-skill-to-pal)
* [Update PAL skill](/api-reference/pal-skills/update-pal-skill)
* [Detach skill from PAL](/api-reference/pal-skills/detach-skill-from-pal)
* [Replace PAL skills](/api-reference/pal-skills/replace-pal-skills)

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:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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:

```bash theme={null}
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:

```bash theme={null}
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": {} } } }'
```

<Warning>
  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.
</Warning>

Skills that were already attached keep their original `attached_at`.

### Detaching

```bash theme={null}
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` | More than 50 curated images | `image.images must have at most 50 entries` |
| `400` | A curated image `caption` is longer than 280 characters | `image caption must be at most 280 characters` |
| `400` | A curated image `prompt` is longer than 500 characters | `image prompt must be at most 500 characters` |
| `400` | A curated `document_id` isn't accessible to the PAL's owner | `One or more document_ids are not accessible` |
| `400` | A curated `document_id` hasn't finished processing | `One or more document_ids are not ready for display` |
| `400` | `web.enabled` is `true` with an empty `domains` | `image.web.enabled requires at least one entry in image.web.domains` |
| `400` | More than 20 allowlisted domains | `image.web.domains must have at most 20 entries` |
| `400` | `web.guidance` is longer than 500 characters | `image.web.guidance must be at most 500 characters` |
| `400` | A `domains` entry has a scheme, port, path, or userinfo | `image.web.domains entry '<entry>' must be a bare hostname (no scheme, port, path, or userinfo)` |
| `400` | A `domains` entry is an IP address, in any spelling | `image.web.domains entry '<entry>' is an IP address (<ip>); allowlist a hostname instead` |
| `400` | A `domains` entry is `localhost` or an internal-network name | `image.web.domains entry '<entry>' is not a public hostname` |
| `400` | A wildcard entry covers a shared suffix | `image.web.domains entry '*.github.io' would allowlist every site under 'github.io'; wildcard a single site (*.site.example.com) or list the exact hosts instead` |
| `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.
* `image` resolves its curated Knowledge Base documents at this point. If none of them resolve to a displayable image and `web` is not enabled, no `canvas_show_image` action is compiled. With a live `web` allowlist, the compiled action also accepts image URLs.
* `canvas_clear` is added once at least one component is active.
* Canvas actions never overwrite a [tool](/sections/conversational-video-interface/pal/tools) you defined with the same name; your tool wins.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.