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

# Build a custom scheduling integration

> Create Availability and Booking actions, publish a CodeKit integration, and verify it with Meeting Scheduler.

Use this demo to connect your booking card without an external calendar provider. It offers one 30-minute meeting time and returns a **Demo only** confirmation. It creates no calendar event, sends no email, and reserves no real availability.

Use a separate demo toolkit and agent. Replace both actions with your provider integration before offering real bookings.

## Before you start

Before you start, sign in to [the dashboard](https://dashboard.hellotars.com) and select the organization you want to work in.

* You need a role that can manage resources, such as Admin or Member, and permission to publish the demo agent.
* Follow [Write a custom action](https://hellotars.com/docs/guides/tools/write-a-custom-action.md) for toolkit creation, the editor, and deployment controls.
* Read [Book meetings with the Meeting Scheduler](https://hellotars.com/docs/guides/building/gambits/meeting-scheduler.md) for component setup.

## Create the two actions

**1. Create a CodeKit toolkit named Demo scheduling**

Use [Create a CodeKit toolkit](https://hellotars.com/docs/guides/tools/write-a-custom-action.md#create-a-codekit-toolkit) for the toolkit controls.

**2. Add the Demo availability action**

Choose **Scheduling · Availability** as its **Action type**.

**3. Replace the Availability starter code**

Paste this code into the CodeKit editor:

```typescript theme={null}
import { type Input, type Output } from 'tars'

const MINUTE_MS = 60 * 1000
const SLOT_MS = 30 * MINUTE_MS

async function run(input: Input): Promise<Output> {
  const formatter = new Intl.DateTimeFormat('en-US', {
    timeZone: input.timezone,
    year: 'numeric', month: '2-digit', day: '2-digit',
    hour: '2-digit', minute: '2-digit', hourCycle: 'h23'
  })
  function localTime(utc: number) {
    const parts = Object.fromEntries(
      formatter.formatToParts(utc).map(part => [part.type, part.value])
    )
    return {
      date: `${parts.year}-${parts.month}-${parts.day}`,
      minutes: Number(parts.hour) * 60 + Number(parts.minute)
    }
  }

  const earliest = Math.max(input.range.startUtc, Date.now() + 60 * MINUTE_MS)
  let startUtc = Math.ceil(earliest / MINUTE_MS) * MINUTE_MS
  while (startUtc + SLOT_MS <= input.range.endUtc) {
    const start = localTime(startUtc)
    const end = localTime(startUtc + SLOT_MS)
    if (
      start.minutes % 30 === 0 &&
      start.minutes >= 9 * 60 && end.minutes <= 17 * 60 &&
      start.date === end.date && end.minutes === start.minutes + 30
    ) {
      break
    }
    startUtc += (30 - start.minutes % 30) * MINUTE_MS
  }
  const endUtc = startUtc + SLOT_MS
  if (endUtc > input.range.endUtc) {
    return { range: input.range, outcome: { status: 'empty' } }
  }
  return {
    range: input.range,
    outcome: {
      status: 'ready',
      slots: [{
        slotId: `demo_${startUtc}`,
        startUtc,
        endUtc,
        durationMinutes: 30
      }]
    }
  }
}
```

**4. Add the Demo booking action**

Choose **Scheduling · Booking** as its **Action type**.

**5. Replace the Booking starter code**

Paste this code into the CodeKit editor:

```typescript theme={null}
import { type Input, type Output } from 'tars'

async function run(input: Input): Promise<Output> {
  return {
    status: 'confirmed',
    booking: {
      bookingId: `demo_${input.attemptId}`,
      startUtc: input.slot.startUtc,
      endUtc: input.slot.endUtc,
      timezone: input.timezone,
      title: 'Demo only: no calendar event',
      hosts: [],
      status: 'confirmed'
    }
  }
}
```

Keep the schemas supplied by the action types. These actions return the contract objects directly, without the `successful`, `data`, and `error` wrapper used by general actions. Times are UTC milliseconds and ranges have an exclusive end.

## Read the sample inputs and outputs

These examples illustrate the contract values. The availability output assumes the current time is at or before the range start, `1800000000000`.

The action offers the first 30-minute appointment starting at least an hour from now, on a local half-hour boundary. The whole appointment fits between 9 AM and 5 PM in the end user's timezone. Later calls offer a time based on the current clock, or return `empty` if no appointment fits the range.

The scheduler supplies the real integration ID, version, range, chosen time, and attempt ID at runtime. The `Input` and `Output` types come from the CodeKit editor's generated `tars` module. Paste the actions into that editor. These snippets are not a standalone local TypeScript project.

> [accordion] Availability input and expected output
>
> Input:
>
> ```json theme={null}
> {
>   "integration": { "integrationId": "demo_scheduling", "revision": "example_revision" },
>   "timezone": "UTC",
>   "range": { "startUtc": 1800000000000, "endUtc": 1800086400000 }
> }
> ```
>
> Expected output:
>
> ```json theme={null}
> {
>   "range": { "startUtc": 1800000000000, "endUtc": 1800086400000 },
>   "outcome": {
>     "status": "ready",
>     "slots": [{
>       "slotId": "demo_1800003600000",
>       "startUtc": 1800003600000,
>       "endUtc": 1800005400000,
>       "durationMinutes": 30
>     }]
>   }
> }
> ```

> [accordion] Booking input and expected output
>
> Input:
>
> ```json theme={null}
> {
>   "integration": { "integrationId": "demo_scheduling", "revision": "example_revision" },
>   "timezone": "UTC",
>   "slotId": "demo_1800003600000",
>   "slot": {
>     "slotId": "demo_1800003600000",
>     "startUtc": 1800003600000,
>     "endUtc": 1800005400000,
>     "durationMinutes": 30
>   },
>   "attemptId": "example_attempt"
> }
> ```
>
> Expected output:
>
> ```json theme={null}
> {
>   "status": "confirmed",
>   "booking": {
>     "bookingId": "demo_example_attempt",
>     "startUtc": 1800003600000,
>     "endUtc": 1800005400000,
>     "timezone": "UTC",
>     "title": "Demo only: no calendar event",
>     "hosts": [],
>     "status": "confirmed"
>   }
> }
> ```

The CodeKit **Test** tab cannot run scheduling actions. Builder test conversations simulate scheduling, so they do not verify that your published action code executes.

## Publish the integration

**1. Deploy and enable both actions**

Save and deploy **Demo availability** and **Demo booking**, then enable each action.

**2. Open the Publish integration dialog**

Open **Integrations** in the toolkit's more-options menu and select **Publish integration**.

**3. Publish the integration**

Choose the **Scheduling** contract. Set **Availability action** to **Demo availability** and **Booking action** to **Demo booking**. Set **Name** to **Demo scheduling** and **Integration ID** to `demo_scheduling`, then select **Publish**.

**4. Select the version in Meeting Scheduler**

On a separate demo agent, add a Component gambit in Workflow Mode and pick **Meeting Scheduler**. Open **Configure** → **Integration** and select the published **Demo scheduling** version. Under **Inputs**, set `prefillName` to `Demo Guest` and `prefillEmail` to `guest@example.com` for this demo. Both are required for a CodeKit scheduler.

Save the gambit and connect it into the flow.

For a real integration, replace Availability with a read from your provider and Booking with a verified provider write. Pass `attemptId` to the provider as an idempotency key so retrying cannot create duplicate meetings. Return `confirmed` only after verifying the booking, `rejected` when nothing was created, and `unknown` with the attempt ID when a write may have succeeded but cannot be verified. The **Contract guide** lists the complete schemas and other outcomes.

After changing either action, deploy it and publish a new integration version. Existing schedulers keep their selected version until you select the new one. See [Book meetings with the Meeting Scheduler](https://hellotars.com/docs/guides/building/gambits/meeting-scheduler.md).

## Verify

The demo actions do not create calendar events or send emails. Use only the separate demo agent for this check.

**1. Publish the demo agent**

The published web chat runs the CodeKit actions instead of the builder's simulated scheduler.

**2. Open the demo agent's web chat and view available times**

Viewing available times calls **Demo availability**.

**3. Select and confirm an available time**

This calls **Demo booking**. The confirmation reads **Demo only: no calendar event**.

**4. Open the toolkit's Logs and inspect both calls**

Both calls show **Succeeded**. Scheduling integration calls keep no payloads, so the input and output panels show `[redacted]`. Check each call by its status and the web chat confirmation.

## Related pages

* [Write a custom action](https://hellotars.com/docs/guides/tools/write-a-custom-action.md)
* [Book meetings with the Meeting Scheduler](https://hellotars.com/docs/guides/building/gambits/meeting-scheduler.md)
* [Monitor tool calls](https://hellotars.com/docs/guides/tools/monitor-tool-calls.md)


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