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

# Custom calendar

> Connect your own calendar or scheduling API to telli so agents can fetch availability and book appointments through custom endpoints.

<img src="https://mintcdn.com/telli/B1-jcrEXZ5AA-uwV/images/integrations/telli-custom-calendar-light.svg?fit=max&auto=format&n=B1-jcrEXZ5AA-uwV&q=85&s=3c4174bb69834968827966a3ab2c9904" className="block dark:hidden" alt="telli + Custom calendar integration" width="1200" height="360" data-path="images/integrations/telli-custom-calendar-light.svg" />

<img src="https://mintcdn.com/telli/B1-jcrEXZ5AA-uwV/images/integrations/telli-custom-calendar-dark.svg?fit=max&auto=format&n=B1-jcrEXZ5AA-uwV&q=85&s=a169c305bf3a522875086a591adbbfd1" className="hidden dark:block" alt="telli + Custom calendar integration" width="1200" height="360" data-path="images/integrations/telli-custom-calendar-dark.svg" />

## Overview

The custom calendar option lets you connect your existing calendar or appointment system with telli agents.

Use this when you want telli to work with your own booking infrastructure instead of a built-in provider.

## Before you start

* A telli agent that should book appointments
* An API endpoint to return available appointment slots
* An API endpoint to book a selected appointment slot
* Publicly reachable endpoints for telli to call

## Set up a custom calendar in telli

<Steps>
  <Step title="Open the agent">
    Open the agent you want to configure in telli.
  </Step>

  <Step title="Choose Generic Calendar">
    In **Calendar Integration**, select **Generic Calendar**.
  </Step>

  <Step title="Add your endpoints">
    Provide the **Available URL** and, if telli should complete bookings, the **Book URL** for your scheduling API.
  </Step>

  <Step title="Save the agent">
    Save the agent so telli can request availability and book appointments through your endpoints.
  </Step>
</Steps>

## Request flow

```mermaid theme={null}
sequenceDiagram
    participant Customer
    participant telli Agent
    participant Your System

    Customer->>telli Agent: "I'd like to make an appointment"
    telli Agent->>Your System: Request available slots
    Your System-->>telli Agent: Return list of time slots
    telli Agent->>Customer: Present available appointments
    Customer->>telli Agent: Select preferred time
    telli Agent->>Your System: Book selected slot
    Your System-->>telli Agent: Confirm booking
    telli Agent->>Customer: Confirm appointment
```

## Endpoints

For full appointment scheduling, implement both endpoints below. If you only want telli to fetch availability, the booking endpoint can stay unset.

### Get available slots

This endpoint returns a list of available appointment slots.

```json theme={null}
POST /available

{
  "contact": {
    "id": "telli contact identifier",
    "type": "Contact",
    "externalId": "your contact identifier",
    "externalUrl": null,
    "salutation": null,
    "firstName": "Ada",
    "lastName": "Lovelace",
    "phoneNumber": "+4915112345678",
    "timezoneIana": "Europe/Berlin",
    "email": "ada@example.com",
    "createdAt": "2026-03-13T09:00:00.000Z",
    "updatedAt": "2026-03-13T09:00:00.000Z",
    "properties": [
      {
        "key": "appointment_type",
        "value": "demo",
        "dataType": "select",
        "label": "Appointment Type"
      }
    ]
  },
  // Deprecated legacy field, kept for backwards compatibility.
  "contact_id": "telli contact identifier",
  // Deprecated legacy field, kept for backwards compatibility.
  "external_contact_id": "your contact identifier",
  // Deprecated legacy field, kept for backwards compatibility.
  "contact_details": {
    "foobar": "baz"
  }
}
```

```json theme={null}
{
  "available": [
    {
      "start_iso": "2024-01-01T10:00:00.000",
      "end_iso": "2024-01-01T10:30:00.000"
    }
  ]
}
```

### Book appointment

This endpoint handles the actual booking of a selected time slot.

```json theme={null}
POST /book

{
  "contact": {
    "id": "telli contact identifier",
    "type": "Contact",
    "externalId": "your contact identifier",
    "externalUrl": null,
    "salutation": null,
    "firstName": "Ada",
    "lastName": "Lovelace",
    "phoneNumber": "+4915112345678",
    "timezoneIana": "Europe/Berlin",
    "email": "ada@example.com",
    "createdAt": "2026-03-13T09:00:00.000Z",
    "updatedAt": "2026-03-13T09:00:00.000Z",
    "properties": [
      {
        "key": "appointment_type",
        "value": "demo",
        "dataType": "select",
        "label": "Appointment Type"
      }
    ]
  },
  // Deprecated legacy field, kept for backwards compatibility.
  "contact_id": "telli contact identifier",
  // Deprecated legacy field, kept for backwards compatibility.
  "external_contact_id": "your contact identifier",
  "start_iso": "2024-01-01T10:00:00.000",
  // Deprecated legacy field, kept for backwards compatibility.
  "contact_details": {
    "foobar": "baz"
  }
}
```

```json theme={null}
{
  "status": "success"
}
```

```json theme={null}
{
  "status": "failed",
  "reason": "Appointment slot is no longer available"
}
```

## Implementation notes

* `contact` is the preferred field for contact data and follows the V2 contact shape, including typed `properties`
* `contact_id`, `external_contact_id`, and `contact_details` are deprecated legacy fields that are still sent for backwards compatibility
* `contact_details` contains the old flat key-value payload from V1 dynamic variables
* Use UTC timestamps in ISO 8601 format
* telli uses `start_iso` as the slot identifier
* Return HTTP 200 responses and indicate success or failure in the response body
* Make sure telli can reach your endpoints

## Request authentication

If you want to verify requests from telli, check the `x-telli-signature` header.

```javascript theme={null}
const crypto = require("crypto");

function verifyRequest(payload, signature, apiKey) {
  const expectedSignature = crypto
    .createHmac("sha256", apiKey)
    .update(JSON.stringify(payload))
    .digest("hex");

  return signature === expectedSignature;
}
```
