Skip to main content
The calendar component displays a date or time picker mid-conversation. The user’s selection is delivered to your webhook as structured data: a YYYY-MM-DD string or an ISO-8601 slot.
The calendar shows only the dates and slots in the invocation; it does not fetch availability. For live booking against a Calendly scheduling page, use scheduling_embed.
calendar is enabled whenever the Magic Canvas skill is attached and has no component-specific configuration; see Enabling and configuring components.

Modes

modeWhat the user seesWhat comes back on submit
date_pickerA month calendar, optionally with quick-pick preset buttonsselected_date (YYYY-MM-DD)
slot_pickerA list of concrete time slotsselected_slot (or selected_slots with multi_select)
date_timeCalendly-style: pick a date, then a slot on that dateselected_slot (or selected_slots with multi_select)
rangeA start–end date range pickerselected_range ({start, end})

PAL Behavior

The PAL is prompted to invoke canvas_show_calendar when the user should pick a date or time slot, inferring mode, multi_select, date_range, initial_month, and initial_year from the conversation. You don’t trigger the action directly. Slot times come from the PAL’s context. Provide availability through your system prompt, conversational context, or a tool from your Tools library.

Arguments

The same fields appear in update_component patches; dates use YYYY-MM-DD.
FieldTypeRequiredDescription
titlestringHeading on the card. 1–120 characters.
timezonestringIANA timezone name (e.g. America/Los_Angeles) used to present times. 1–120 characters.
modestringdate_picker, slot_picker, date_time, or range. If omitted: slots present → slot_picker, otherwise date_picker.
multi_selectbooleanSlot-bearing modes only (slot_picker, date_time): let the user pick more than one slot. Defaults to false.
slotsarray✅ when mode is slot_picker or date_time1–24 entries. Each slot: id (required), start (required, ISO-8601), end (required, ISO-8601), label (optional display text).
presetsarrayUp to 6 quick-pick buttons for date_picker. Each: label (required, ≤40 chars) and date (required, YYYY-MM-DD).
initial_datestringPre-selected date, YYYY-MM-DD.
initial_monthstringMonth the calendar opens on, YYYY-MM.
initial_yearintegerYear the calendar opens on, 1–9999.
date_rangeobject{ "start", "end" }, both YYYY-MM-DD, inclusive. Limits which dates are selectable.
highlighted_datesarray of stringsUp to 62 YYYY-MM-DD dates to visually highlight.
allow_skipbooleanShows a skip button so the user can decline to pick. Defaults to false.
layoutobjectPlacement control available on every component action: { "preferred_slot": "safe-area-right" | "safe-area-left" }. Calendar cards default to safe-area-right.
display_modestring"inline" (the only supported value). Available on every component action.
Example invocation
{
  "title": "Pick a demo time",
  "timezone": "America/Los_Angeles",
  "mode": "slot_picker",
  "slots": [
    {
      "id": "slot-1",
      "label": "Tuesday, 10:00 AM",
      "start": "2026-05-26T10:00:00-07:00",
      "end": "2026-05-26T10:30:00-07:00"
    },
    {
      "id": "slot-2",
      "label": "Tuesday, 2:00 PM",
      "start": "2026-05-26T14:00:00-07:00",
      "end": "2026-05-26T14:30:00-07:00"
    }
  ]
}

Interactions

calendar produces six interaction types: submit, skip, dismiss, clear, error, and heartbeat. Each is delivered to your conversation webhook as a canvas.interaction event and is also available from GET https://tavusapi.com/v2/conversations/{conversation_id}/canvas/interactions.
Webhook delivery
{
  "message_type": "canvas",
  "event_type": "canvas.interaction",
  "conversation_id": "c123abc456def",
  "timestamp": "2026-05-26T17:03:12Z",
  "properties": {
    "conversation_id": "c123abc456def",
    "interaction_id": "ci_tc-8842_submit_1c9f2e7a",
    "tool_call_id": "tc-8842",
    "component": "canvas.calendar",
    "component_version": "v1",
    "type": "submit",
    "value": {
      "selected_date": null,
      "selected_slot": {
        "id": "slot-1",
        "label": "Tuesday, 10:00 AM",
        "start": "2026-05-26T10:00:00-07:00",
        "end": "2026-05-26T10:30:00-07:00"
      },
      "skipped": false
    },
    "metadata": {},
    "created_at": "2026-05-26T17:03:12.482910"
  }
}

submit

Exactly one selection field is present and non-null, and skipped is false. The field depends on the card’s mode:
ModeSelection field
date_pickerselected_date, a YYYY-MM-DD string
slot_picker / date_timeselected_slot, one slot object
slot_picker / date_time with multi_selectselected_slots, an array of one or more slot objects
rangeselected_range, { "start", "end" }
Slot objects echo back exactly what the PAL offered: id, start, and end always; label when the invocation included one. Selection fields for other modes are null.

skip

Sent when allow_skip is on and the user taps the skip button: skipped is true and no selection field carries a value. The PAL sees the skip too.
{
  "selected_date": null,
  "selected_slot": null,
  "skipped": true
}

dismiss, clear, error, heartbeat

Lifecycle events about the card itself, not answers:
TypeMeaning
dismissThe user closed the card without answering.
clearThe card was removed (for example, the PAL called canvas_clear or replaced it).
errorThe renderer reported a problem with the card.
heartbeatA periodic liveness signal while the card is on screen.
Their value has no fixed schema (a free-form object, often {}); don’t build logic on its contents.
Don’t count a conversation as scheduled until you see a submit; users also dismiss, skip, or let the conversation move on.

Webhook Handling

Branch on type first, then on whichever selection field is non-null:
app.post("/tavus-webhook", (req, res) => {
  const { event_type, properties } = req.body;

  if (
    event_type !== "canvas.interaction" ||
    properties.component !== "canvas.calendar"
  ) {
    return res.sendStatus(200);
  }

  const { type, value } = properties;

  if (type === "submit") {
    if (value.selected_date) {
      bookDate(value.selected_date);
    } else if (value.selected_slot) {
      bookSlot(value.selected_slot.start, value.selected_slot.end);
    } else if (value.selected_slots) {
      value.selected_slots.forEach((s) => bookSlot(s.start, s.end));
    } else if (value.selected_range) {
      bookRange(value.selected_range.start, value.selected_range.end);
    }
  } else if (type === "skip" || type === "dismiss") {
    markDeclined(properties.conversation_id);
  }

  res.sendStatus(200);
});
Tavus records each interaction once: a retried POST with the same interaction_id and identical payload never fires your webhook twice. Key non-reversible handlers on properties.interaction_id anyway.

Reference

Component idcanvas.calendar
Action namecanvas_show_calendar
Versionv1
Interaction classsubmit_capable
Interaction typessubmit, skip, dismiss, clear, error, heartbeat
Default placementsafe-area-right
Supports update_componentYes
PAL configOn by default with the magic_canvas skill attached; disable with config.components.calendar = { "enabled": false }