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

# Calendar & booking

> Let assistants check availability and book appointments mid-call — via Acuity Scheduling, Cal.com, Calendly, Google Calendar, Outlook, or the built-in booking engine

Appointment scheduling is the classic voice-agent use case: the assistant checks open slots during the call, offers a few options, and books the one the caller picks. The platform supports this in two ways that can be combined freely:

1. **Calendar integrations** — connect an external scheduling provider (Acuity Scheduling, Cal.com, Calendly, Google Calendar, Outlook) once, assign it to an assistant, and the assistant automatically gets booking tools for every call.
2. **The built-in booking engine** — define your own event types with weekly availability and get a public, embeddable booking page at `/book/{workspace}/{slug}`, ICS invitation emails, and a `native` integration your assistants can book against. No external account required.

## Providers at a glance

| Provider                     | Availability                                                       | Booking                                                                                    | Credentials                               |
| ---------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | ----------------------------------------- |
| **Cal.com**                  | ✓ open slots of an event type                                      | ✓ direct booking                                                                           | API key (`cal_…`) + numeric event type ID |
| **Calendly**                 | ✓ available times of a selected event type                         | ✓ direct booking (paid Calendly plans), single-use scheduling link, confirmed cancellation | OAuth connect (one time)                  |
| **Acuity Scheduling**        | ✓ live slots or class availability for a selected appointment type | ✓ direct booking, confirmed cancellation and rescheduling (series cannot be rescheduled)   | OAuth connect (one time)                  |
| **Google Calendar**          | ✓ free/busy of a connected calendar                                | ✓ event creation with attendee invite                                                      | OAuth connect (one time)                  |
| **Outlook / Microsoft 365**  | ✓ free/busy via Microsoft Graph                                    | ✓ event creation with attendee invite                                                      | OAuth connect (one time)                  |
| **Native (built-in engine)** | ✓ computed from your event type's weekly availability              | ✓ direct booking + ICS email                                                               | none                                      |

<Note>
  **Calendly link mode**: Calendly's Scheduling API requires a paid Calendly plan. If your plan cannot book directly, set the integration's `booking_mode` to `link` — the assistant then agrees on a rough time with the caller and sends a **single-use scheduling link** by SMS or email (`link_channel`) instead of hard-booking. Integrations that hit the paid-plan restriction at call time are flagged with status `link_mode`.
</Note>

## Connecting an integration

Go to **Booking → Integrations** and pick a provider card:

* **Cal.com** — paste your API key (Cal.com → Settings → Developer → API Keys) and the numeric event type ID (visible in the event type URL). Optional timezone override — make sure it matches the Cal.com event type.
* **Calendly** — click **Connect with Calendly**, approve access, then choose an active event type by **name and duration**. The account connection can be reused by multiple integrations; each integration selects exactly one event type. Choose the booking mode (`api` or `link`), link channel, and Book/Cancel permissions. Full Calendly resource URIs and rotating refresh tokens stay internal.
* **Acuity Scheduling** — click **Connect with Acuity**, approve the `api-v1` access request, then choose an appointment type. Optionally select one specific Acuity calendar/person; choose **Any available calendar** to let Acuity route each booking to an available calendar offering that appointment type. Enable or disable Book, Cancel and Reschedule per integration; Reschedule is forced off for series because Acuity's client API rejects moving a series. OAuth tokens stay server-side and are never returned by the API.
* **Google / Outlook** — click **Connect** and complete the OAuth consent. The connection is stored per workspace and reused by every integration and event type that references it.
* **Native** — pick one of your booking event types (see below).

Every integration is **verified before it is saved**: an invalid API key, OAuth connection, or event type ID is rejected with a clear error and never stored. Secret values never leave the server — responses mask them as `•••` (send `•••` on update to keep a stored legacy secret).

Deleting the final integration that uses an Acuity account revokes its OAuth token through Acuity's disconnect endpoint and removes the local connection. An unused account can also be removed with **Disconnect account** in the Acuity editor; shared accounts cannot be disconnected until their remaining integrations are removed.

<Note>
  For a self-managed Calendly developer app, register
  `https://www.ouraicalling.de/api/oauth/calendly/callback` as the production
  **Redirect URI**. Enable `users:read`, `event_types:read`, `locations:read`,
  `scheduled_events:write`, and `scheduling_links:write`. Webhook scopes and the
  webhook signing key are not required for this connection.
</Note>

Existing personal-access-token integrations remain operational but appear as
**Legacy connection — reconnect with Calendly**. Reconnecting upgrades them to
OAuth and removes the PAT from the integration.

<Note>
  For the Acuity OAuth client, register
  `{OAUTH_REDIRECT_BASE_URL}/api/mcp-connectors/callback` as an exact redirect
  URI. The callback recognizes Acuity's namespaced, single-use state before the
  generic MCP connector handler and returns the browser to the original
  workspace domain. Configure the client with `ACUITY_OAUTH_CLIENT_ID` and
  `ACUITY_OAUTH_CLIENT_SECRET`; set `ACUITY_OAUTH_REDIRECT_URI` only when you
  need to pin one exact registered URI. Never expose the client secret in browser
  code. Starting this interactive OAuth flow requires a user-bound owner/admin
  credential; service-account credentials are intentionally rejected.
</Note>

## Assigning to an assistant

Open the assistant's settings and tick the integrations it should use (or `PUT /api/v1/assistants/{id}/integrations`). For **every assigned integration** the assistant gets these tools on every call:

| Tool                                                           | Type                                                 | What it does                                                                                                                                                                               |
| -------------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `check_availability(start_date, end_date?)`                    | read-only, interruptible                             | Fetches open slots for the date range and reads them out in the assistant's timezone (capped so the agent never recites 200 slots).                                                        |
| `book_appointment(name, email, start, notes?)`                 | write — runs with a filler phrase, not interruptible | Books the chosen slot. On success the booking start/ID are stored as call variables for flows, analysis, and webhooks. If the slot was just taken, the agent is told to offer another one. |
| `send_booking_link(email?, phone?)`                            | Calendly link mode only                              | Creates a single-use scheduling link and sends it via SMS or email.                                                                                                                        |
| `find_appointment(email, name)`                                | Calendly/Acuity management                           | Finds upcoming appointments for the selected event/appointment type. Both the exact booking email and full name are required.                                                              |
| `cancel_appointment(event_id / appointment_id, confirmed)`     | write — not interruptible                            | Cancels only an event or appointment returned by `find_appointment` during the same call, after the assistant reads it back and receives explicit confirmation.                            |
| `reschedule_appointment(appointment_id, new_start, confirmed)` | Acuity management                                    | Moves only an Acuity appointment returned in the same call, after availability was checked and the caller explicitly confirmed the new time.                                               |

If more than one integration is assigned, tool names get the integration name as a suffix (for example `check_availability_sales`). Slots are always spoken in the **assistant timezone** — set it in the assistant's settings.

<Tip>
  Tell the assistant **when** to book in its prompt, e.g.: *"Before offering any time, call check\_availability. Once the caller confirms a slot, call book\_appointment with their name and email."*
</Tip>

## The built-in booking engine

Create event types under **Booking** in the dashboard (or via API/MCP):

* **Name, slug, duration** — the slug is unique within the workspace and becomes the public page URL `/book/{workspace}/{slug}` (`workspace` = the tenant `booking_handle`).
* **Weekly availability** — time windows per weekday in the event type's timezone, e.g. Mon–Fri 09:00–17:00.
* **Buffers & rules** — buffer before/after each booking, minimum notice, booking horizon (`max_days_ahead`), and slot increment.
* **Calendar sync** (optional) — link a connected Google/Outlook calendar: its busy times are subtracted from the offered slots, and confirmed bookings are pushed as calendar events (attendees get the provider's invite).

### Public booking page & embed

Each active event type has a tenant-branded public page at `https://<your-domain>/book/{workspace}/{slug}` — no login required. Embed it anywhere:

```html theme={null}
<iframe src="https://<your-domain>/book/acme/intro-call"
        style="width:100%;min-height:640px;border:0" loading="lazy"></iframe>
```

Visitors pick a slot (shown in their own timezone), enter name and email, and receive a **confirmation email with an ICS calendar invitation** plus a cancellation link. Double bookings are impossible — a database-level exclusion constraint guards the slot even when a web visitor and an assistant book at the same moment; the loser gets a friendly "slot just taken" message.

### Booking from calls

Create an integration with provider **`native`** pointing at the event type and assign it to an assistant — mid-call bookings then land in the same calendar with `source: "call"` and a link to the call record.

## Plan gating

The whole feature is gated by the **`calendar_integrations`** plan flag (platform admin → Plans). Without it, creating integrations or event types returns `403`; existing public booking pages stop accepting new bookings.

## API & MCP

Everything above is available in the [public REST API](/api-reference/introduction) and as MCP tools at `https://<your-domain>/mcp`:

`GET /api/v1/bookings` and `list_bookings` support event type, source and date filters. Use `view=upcoming|unconfirmed|recurring|past|cancelled` for the same booking views as the dashboard, or use an exact `status` filter; `view` and `status` are mutually exclusive.

| REST                                                                                        | MCP tool                                                                                                         | Scope                              |
| ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
| `GET/POST /api/v1/integrations`, `GET/PATCH/DELETE /api/v1/integrations/{id}`               | `list_integrations`, `get_integration`, `create_integration`, `update_integration`, `delete_integration`         | `integrations:read/write`          |
| `POST /api/v1/integrations/calendly/oauth-url`                                              | `create_calendly_oauth_url`                                                                                      | `integrations:write`               |
| `GET /api/v1/integrations/calendly/connections`                                             | `list_calendly_connections`                                                                                      | `integrations:read`                |
| `GET /api/v1/integrations/calendly/event-types?connection_id=…`                             | `list_calendly_event_types`                                                                                      | `integrations:read`                |
| `POST /api/v1/integrations/acuity/oauth-url`                                                | `create_acuity_oauth_url`                                                                                        | `integrations:write`               |
| `GET /api/v1/integrations/acuity/connections`                                               | `list_acuity_connections`                                                                                        | `integrations:read`                |
| `GET /api/v1/integrations/acuity/appointment-types?connection_id=…`                         | `list_acuity_appointment_types`                                                                                  | `integrations:read`                |
| `GET /api/v1/integrations/acuity/calendars?connection_id=…`                                 | `list_acuity_calendars`                                                                                          | `integrations:read`                |
| `GET/PUT /api/v1/assistants/{id}/integrations`                                              | `get_assistant_integrations`, `set_assistant_integrations`                                                       | `integrations:*` or `assistants:*` |
| `GET/POST /api/v1/booking-event-types`, `GET/PATCH/DELETE /api/v1/booking-event-types/{id}` | `get_booking_event_types`, `create_booking_event_type`, `update_booking_event_type`, `delete_booking_event_type` | `bookings:read/write`              |
| `GET /api/v1/bookings`, `GET /api/v1/bookings/{id}`, `POST /api/v1/bookings/{id}/cancel`    | `list_bookings`, `get_booking`, `cancel_booking`                                                                 | `bookings:read/write`              |

Cancelling a booking sends a `METHOD:CANCEL` ICS update, so the appointment disappears from the invitee's calendar automatically.
