Skip to main content
MindStudio
Pricing
Blog About
My Workspace

How to Build a Voice Agent That Books Appointments via Cal.com

Connect an ElevenLabs voice agent to Cal.com using Claude Code to automatically check availability and book discovery calls from your website.

MindStudio Team RSS
How to Build a Voice Agent That Books Appointments via Cal.com

Why Voice Agents Are Replacing Booking Forms

Nobody enjoys filling out a scheduling form. You pick a time, get a confirmation email, then wait. The whole process feels cold — especially when someone just wants a quick discovery call.

Voice agents solve this differently. A visitor lands on your site, speaks to an AI, and walks away with a confirmed appointment — no clicks, no forms, no friction. The technology to build this is mature enough now that you can wire it together in an afternoon.

This guide walks through how to build a voice agent that books appointments via Cal.com. You’ll connect an ElevenLabs conversational agent to the Cal.com API, handle availability checks in real time, and deploy it to your website. By the end, you’ll have a working system that can schedule discovery calls automatically — even while you sleep.


What You’ll Need Before You Start

Before touching any code, make sure you have accounts and access to the right tools.

Required accounts:

  • Cal.com — free tier works fine for this
  • ElevenLabs — for the voice agent and speech synthesis
  • Claude Code (Anthropic’s agentic coding tool) — this is what we’ll use to wire everything together
  • A website or web app where you’ll embed the agent

API keys you’ll need:

  • Cal.com API key (found in Settings → Developer → API Keys)
  • ElevenLabs API key
  • Anthropic API key (for Claude)

Assumptions:

  • You’re comfortable with basic terminal/command line usage
  • You have Node.js installed locally
  • You understand what a webhook is, at least in general terms
RWORK ORDER · NO. 0001ACCEPTED 09:42
YOU ASKED FOR
Sales CRM with pipeline view and email integration.
✓ DONE
REMY DELIVERED
Same day.
yourapp.msagent.ai
AGENTS ASSIGNEDDesign · Engineering · QA · Deploy

If you want a no-code path instead, skip to the MindStudio section later in this article.


How the System Works

Before building anything, it helps to understand the flow.

When a visitor speaks to your voice agent, here’s what happens under the hood:

  1. The visitor speaks — ElevenLabs captures audio via the browser
  2. Speech-to-text converts their words into a transcript
  3. The AI agent reasons about the intent — are they asking about availability? Trying to book a specific time?
  4. The agent calls Cal.com’s API to check open slots or create a booking
  5. The agent responds in natural speech, confirming the appointment or offering alternatives
  6. Cal.com fires a confirmation email to both parties automatically

The key insight is that this isn’t just a voice interface bolted onto a booking form. The agent understands context, handles back-and-forth conversation, and resolves ambiguity before making the API call.


Set Up Cal.com for API Access

Cal.com’s API is well-documented and straightforward. Here’s how to get it ready.

Create a bookable event type

Log into Cal.com and create an event type for your discovery call. Set the duration, add any questions you want answered before the booking, and configure your availability hours.

Note your event type ID — you’ll need it for API calls. You can find it in the URL when editing the event: cal.com/event-types/[ID]/edit.

Generate your API key

Go to Settings → Developer → API Keys and create a new key. Copy it somewhere safe — Cal.com won’t show it again.

Test the availability endpoint

Run a quick test to confirm your setup works:

curl -X GET \
  "https://api.cal.com/v1/slots?apiKey=YOUR_KEY&eventTypeId=YOUR_EVENT_ID&startTime=2025-01-20T00:00:00Z&endTime=2025-01-27T00:00:00Z" \
  -H "Content-Type: application/json"

If you get back a JSON object with available time slots, you’re good. If not, double-check your event type ID and API key.

Key endpoints you’ll use

EndpointMethodWhat it does
/v1/slotsGETReturns available time slots
/v1/bookingsPOSTCreates a new booking
/v1/bookings/{id}GETRetrieves booking details
/v1/bookings/{id}/cancelDELETECancels a booking

Build the Voice Agent with ElevenLabs

ElevenLabs Conversational AI lets you create an agent with a custom voice, a system prompt, and tool-calling capabilities. The tool-calling part is what connects it to Cal.com.

Configure your agent

In the ElevenLabs dashboard, create a new Conversational AI agent. Set:

  • Name — something like “Scheduling Assistant”
  • Voice — pick one that matches your brand tone
  • First message — what the agent says when someone opens the widget (e.g., “Hi! I can help you book a discovery call. Want to check availability?”)

Write a tight system prompt

Your system prompt defines how the agent behaves. Keep it focused:

You are a scheduling assistant for [Company Name]. 
Your only job is to help visitors book a 30-minute discovery call.

When helping someone book:
1. Ask for their preferred date and time range
2. Check availability using the check_availability tool
3. Confirm a specific slot before booking
4. Collect their name and email address
5. Create the booking using the create_booking tool
6. Confirm the appointment details clearly

If asked about anything unrelated to scheduling, politely redirect.
Do not make up availability — always use the tools.

Not a coding agent. A product manager.

Remy doesn't type the next file. Remy runs the project — manages the agents, coordinates the layers, ships the app.

BY MINDSTUDIO

Short, directive prompts work better than long ones for task-focused agents.

Define the tools

ElevenLabs supports tool definitions in OpenAI function-calling format. You’ll define two tools: one for checking availability and one for creating bookings.

check_availability tool:

{
  "name": "check_availability",
  "description": "Check available time slots for booking a discovery call",
  "parameters": {
    "type": "object",
    "properties": {
      "start_time": {
        "type": "string",
        "description": "Start of the search window in ISO 8601 format"
      },
      "end_time": {
        "type": "string",
        "description": "End of the search window in ISO 8601 format"
      }
    },
    "required": ["start_time", "end_time"]
  }
}

create_booking tool:

{
  "name": "create_booking",
  "description": "Book a discovery call at a specific time",
  "parameters": {
    "type": "object",
    "properties": {
      "start_time": {
        "type": "string",
        "description": "Booking start time in ISO 8601 format"
      },
      "name": {
        "type": "string",
        "description": "Guest's full name"
      },
      "email": {
        "type": "string",
        "description": "Guest's email address"
      }
    },
    "required": ["start_time", "name", "email"]
  }
}

Build the Backend with Claude Code

The voice agent needs somewhere to send tool calls. You’ll build a small API server that receives those calls and proxies them to Cal.com. This is where Claude Code does the heavy lifting.

Scaffold the project

Open Claude Code and give it a clear prompt:

Build a Node.js Express server that:
1. Exposes a POST /tools endpoint
2. Receives tool calls from ElevenLabs in the format { tool_name, parameters }
3. Routes check_availability calls to Cal.com's /v1/slots endpoint
4. Routes create_booking calls to Cal.com's /v1/bookings endpoint
5. Returns responses in the format ElevenLabs expects
6. Uses environment variables for the Cal.com API key and event type ID

Claude Code will generate the scaffolding, install dependencies, and write the handler logic. Review what it produces before running it — particularly the error handling around the Cal.com API calls.

The core handler logic

Once Claude Code generates the base, your tool handler will look roughly like this:

app.post('/tools', async (req, res) => {
  const { tool_name, parameters } = req.body;

  if (tool_name === 'check_availability') {
    const slots = await calClient.getSlots({
      eventTypeId: process.env.CAL_EVENT_TYPE_ID,
      startTime: parameters.start_time,
      endTime: parameters.end_time,
    });
    return res.json({ slots });
  }

  if (tool_name === 'create_booking') {
    const booking = await calClient.createBooking({
      eventTypeId: process.env.CAL_EVENT_TYPE_ID,
      start: parameters.start_time,
      responses: {
        name: parameters.name,
        email: parameters.email,
      },
    });
    return res.json({ bookingId: booking.uid, confirmed: true });
  }

  res.status(400).json({ error: 'Unknown tool' });
});

Deploy the server

For production, deploy to Railway, Render, or Fly.io. Each takes under 10 minutes and gives you a public HTTPS URL — which ElevenLabs requires for webhook tool calls.

Set your environment variables on the platform:

  • CAL_API_KEY
  • CAL_EVENT_TYPE_ID

Connect the server to ElevenLabs

Back in the ElevenLabs dashboard, go to your agent’s tool settings and add the server URL as the webhook endpoint for both tools. ElevenLabs will POST to your server whenever the agent decides to call a tool.


Embed the Voice Widget on Your Website

ElevenLabs provides a JavaScript widget you can drop into any HTML page.

<elevenlabs-convai agent-id="YOUR_AGENT_ID"></elevenlabs-convai>
<script src="https://elevenlabs.io/convai-widget/index.js" async></script>

Place this snippet where you want the widget to appear — a floating button in the corner works well for scheduling assistants.

TIME SPENT BUILDING REAL SOFTWARE
5%
95%
5% Typing the code
95% Knowing what to build · Coordinating agents · Debugging + integrating · Shipping to production

Coding agents automate the 5%. Remy runs the 95%.

The bottleneck was never typing the code. It was knowing what to build.

Style and positioning

You can customize the widget’s appearance with CSS or use ElevenLabs’ theme options in the dashboard. For a scheduling-specific agent, consider:

  • Triggering it after a user spends 30+ seconds on your pricing or contact page
  • Placing it on a dedicated “Book a Call” landing page
  • Pre-loading it after a user clicks a CTA button

The widget handles microphone permissions, audio playback, and the WebSocket connection automatically.


Use MindStudio If You Want a No-Code Path

Not everyone wants to manage a backend server. If you’d rather skip the infrastructure and still get a working voice booking agent, MindStudio handles this without any code.

MindStudio’s Agent Skills Plugin is an npm SDK that lets AI agents — including Claude Code — call over 120 typed capabilities as simple method calls. But for teams who don’t want to write code at all, MindStudio’s visual builder lets you create the same kind of scheduling workflow directly in a browser.

You can build a workflow that:

  • Accepts incoming voice or text input
  • Calls the Cal.com API via MindStudio’s built-in integration layer
  • Returns available slots and confirms bookings
  • Sends confirmation emails without touching a mail server

MindStudio connects to Cal.com alongside 1,000+ other tools — no API wrangling, no server deployments. The average workflow takes 15 minutes to an hour to build, and you can expose it as a webhook endpoint or embed it directly on your site.

If you’re a developer who wants to use Claude Code but offload the plumbing, MindStudio’s Agent Skills Plugin handles rate limiting, retries, and authentication automatically — so your agent focuses on logic, not infrastructure.

You can try MindStudio free at mindstudio.ai.


Test the Full Flow

Before going live, run through the full booking journey manually.

Test cases to cover

Happy path:

  • User asks for availability next week → agent returns slots → user picks one → agent collects name and email → booking confirmed

Edge cases:

  • User asks for a time outside your availability hours
  • User gives an invalid email format
  • Cal.com returns no available slots for the requested window
  • Network timeout on the Cal.com API call

Conversation handling:

  • User changes their mind mid-booking
  • User asks an off-topic question
  • User hangs up mid-conversation (no booking created)

For each case, listen to how the agent responds. Adjust your system prompt if the agent is handling any scenario awkwardly.

Check Cal.com confirms bookings correctly

After a successful test booking, verify:

  • The booking appears in your Cal.com dashboard
  • Both you and the test email receive confirmation emails
  • The calendar invite is correctly formatted with the right timezone

Common Mistakes and How to Fix Them

Timezone confusion

Cal.com returns slots in UTC. If you don’t convert these to the user’s local timezone before the agent reads them aloud, you’ll get confused callers.

Fix: detect the user’s timezone via the browser (Intl.DateTimeFormat().resolvedOptions().timeZone) and pass it as a parameter to your availability check. Format the returned slots in local time before the agent speaks them.

The agent books without confirming

If your system prompt isn’t explicit about confirming before booking, the agent may create bookings the user didn’t finalize. This leads to phantom appointments.

Fix: add a line to your system prompt: “Always repeat the proposed time slot back to the user and wait for explicit confirmation before calling create_booking.”

Slot formatting sounds unnatural

Saying “2025-01-20T14:00:00Z” aloud is terrible UX. Your server should format slots into natural language before returning them to the agent.

Fix: in your tool handler, format the response like: "Monday January 20th at 2:00 PM Eastern" rather than returning raw ISO strings.

Missing email validation

If the user gives a malformed email, Cal.com will reject the booking with a 400 error. Your server should validate the email format before calling the API.

Fix: add a simple regex check in your tool handler and return a friendly error message if the format is wrong.


Frequently Asked Questions

Does this work with Google Calendar and Outlook, or only Cal.com?

Cal.com connects to both Google Calendar and Outlook Calendar natively. When you set up your Cal.com account, you can sync it with your existing calendar so availability checks reflect your real schedule. The voice agent interacts with Cal.com’s API, which handles the calendar syncing behind the scenes.

How do I handle multiple hosts or team scheduling?

Cal.com supports round-robin and collective event types for teams. Create a team event type in Cal.com and use its event type ID in your configuration. The availability logic remains the same — Cal.com handles which team member gets assigned the booking.

Can the voice agent reschedule or cancel existing bookings?

Yes, but you’ll need to add two more tools: reschedule_booking and cancel_booking. Both are supported by the Cal.com API. You’ll also need a way for the agent to look up existing bookings by email so it can find the booking ID before modifying it.

What happens if someone books outside my working hours?

Your Cal.com availability settings define what time slots are returned by the /v1/slots endpoint. If a user requests a time outside those hours, the API simply won’t return that slot. The agent should handle an empty slot response gracefully — something like “I don’t see any availability on that day, can you try a different date?”

Is there a cost to run this at scale?

The main costs are ElevenLabs (priced per minute of conversation), your hosting server (usually $5–10/month for a small Node app), and Cal.com (free tier covers most use cases). ElevenLabs’ pricing depends on usage volume — check their current plans for specifics. At moderate traffic (a few hundred calls per month), you’re looking at $20–50/month total.

How do I pass custom data like company name or use case before the call?

You can pass context to the ElevenLabs widget using dynamic variables at initialization. This lets you pre-populate the conversation with data you already have — like the visitor’s name from a CRM or which product page they’re on. Use the dynamic-variables attribute on the widget or pass them via the JavaScript initialization API.


Key Takeaways

  • Voice booking agents reduce friction compared to forms — visitors speak naturally and walk away with confirmed appointments
  • The core stack is ElevenLabs (voice + conversation) + a Node.js middleware server + Cal.com’s REST API
  • Claude Code accelerates the backend build significantly — use it to scaffold, then review the output carefully
  • Timezone handling, slot formatting, and confirmation logic are the three areas most likely to cause problems in production
  • MindStudio offers a no-code alternative that connects voice agents to Cal.com without backend infrastructure

How Remy works. You talk. Remy ships.

YOU14:02
Build me a sales CRM with a pipeline view and email integration.
REMY14:03 → 14:11
Scoping the project
Wiring up auth, database, API
Building pipeline UI + email integration
Running QA tests
✓ Live at yourapp.msagent.ai

If you want to build this kind of automation without managing a server, MindStudio’s visual builder lets you wire together voice input, API calls, and scheduling logic in a single workflow. It’s free to start and doesn’t require any setup beyond an account.

Presented by MindStudio

No spam. Unsubscribe anytime.