Appointment Scheduler

This component takes advantage of function calling within Vapi assistants.

Preview

Book an appointment with Cam @ Vapi Blocks!

Live example of appointment scheduling. Please cancel if you do not plan on attending meeting with Cam.

Live Transcript

Function Calling

This example leverages Cal.com to check availability and book appointments (for free!). Using Cal.com will sync to Google & Outlook calendars.

checkAvailability

Create a new route for direct use with your function named checkAvailability. Copy the following code to /api/vapi/check-availability/route.tsx

import { NextRequest, NextResponse } from 'next/server';
import axios from 'axios';
 
const bearerToken = process.env.CAL_API_KEY;
const username = process.env.CAL_USERNAME;
const userId = process.env.CAL_USERID;
 
async function checkAvailability(parameters: { dateFrom: string; dateTo: string; }) {
  const { dateFrom, dateTo } = parameters;
  const response = await axios.get('https://api.cal.com/v1/availability', {
    params: {
      apiKey: bearerToken,
      userId: userId,
      username: username,
      dateFrom: dateFrom,
      dateTo: dateTo,
      eventTypeId: '873076'
    }
  });
  return response.data;
}
 
export async function POST(request: NextRequest) {
  console.log(request);
  try {
    const { message } = await request.json();
    console.log(message);
    if (message.type === 'function-call' && message.functionCall) {
      const { parameters } = message.functionCall;
 
      const result = await checkAvailability(parameters);
      const response = NextResponse.json({ result }, { status: 200 });
      response.headers.set('Access-Control-Allow-Origin', '*');
      response.headers.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
      response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
      return response;
    } else {
      const response = NextResponse.json({ message: `Unhandled message type: ${message.type}` }, { status: 400 });
      response.headers.set('Access-Control-Allow-Origin', '*');
      response.headers.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
      response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
      return response;
    }
  } catch (error) {
    console.error('Error processing request:', error);
    const response = NextResponse.json({ message: error }, { status: 500 });
    response.headers.set('Access-Control-Allow-Origin', '*');
    response.headers.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
    response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    return response;
  }
}
 
export async function OPTIONS() {
  const response = NextResponse.json({}, { status: 200 });
  response.headers.set('Access-Control-Allow-Origin', '*');
  response.headers.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
  response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  return response;
}
 
 

Example Payload:

{
    "dateTo": "2024-08-01T06:06:13.371Z",
    "dateFrom": "2024-07-29T06:06:14.371Z"
}

bookAppointment

Copy to api/vapi/book-appointment/route.tsx

import { NextRequest, NextResponse } from 'next/server';
import axios from 'axios';
 
const apiKey = process.env.CAL_API_KEY;
 
async function createBooking({ start, name, email, smsReminderNumber }: { start: string; name: string; email: string; smsReminderNumber?: string }) {
  const startDateTime = new Date(start);
  const endDateTime = new Date(startDateTime.getTime() + 30 * 60000); // Add 30 minutes to start time
  const end = endDateTime.toISOString();
 
  const response = await axios.post(
    `https://api.cal.com/v1/bookings?apiKey=${apiKey}`,
    {
      eventTypeId: 873076,
      start: start,
      end: end,
      responses: {
        name: name,
        email: email,
        metadata: {},
        location: "Google Meet"
      },
      timeZone: "America/Los_Angeles",
      language: "en",
      title: "30min Meeting with Cam (Founder of VapiBlocks)",
      description: "Discuss partnership with VapiBlocks or ways to add Voice AI to your app",
      status: "ACCEPTED",
      smsReminderNumber: smsReminderNumber || null,
      metadata: {}
    }
  );
 
  return response.data;
}
 
export async function POST(request: NextRequest) {
  try {
    const { message } = await request.json();
    console.log(message);
    if (message.type === 'function-call' && message.functionCall) {
      const { parameters } = message.functionCall;
      
      const result = await createBooking(parameters);
      const jsonResponse = NextResponse.json({ result: "The booking was created successfully.", response: result }, { status: 200 });
      jsonResponse.headers.set('Access-Control-Allow-Origin', '*');
      jsonResponse.headers.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
      jsonResponse.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
      return jsonResponse;
    } else {
      const jsonResponse = NextResponse.json({ message: `Unhandled message type: ${message.type}` }, { status: 400 });
      jsonResponse.headers.set('Access-Control-Allow-Origin', '*');
      jsonResponse.headers.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
      jsonResponse.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
      return jsonResponse;
    }
  } catch (error) {
    console.error('Error processing request:', error);
    const jsonResponse = NextResponse.json({ message: error }, { status: 500 });
    jsonResponse.headers.set('Access-Control-Allow-Origin', '*');
    jsonResponse.headers.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
    jsonResponse.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    return jsonResponse;
  }
}
 
export async function OPTIONS() {
  const response = NextResponse.json({}, { status: 200 });
  response.headers.set('Access-Control-Allow-Origin', '*');
  response.headers.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
  response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  return response;
}
 

Example Payload

{
  "start": "2024-09-29T18:00:00.000Z",
  "name": "John Doe",
  "email": "john@vapiblocks.com",
  "smsReminderNumber": ""
}

Vapi Dashboard setup for functions