A freelancer told me: 'I spent 3 hours per week going back and forth over email to schedule meetings. I added Cal.com to my site — now clients book directly. I got 3 hours of my life back every week.'
What Cal.com Offers for Free
Cal.com free tier:
- Unlimited bookings on the free plan
- 1 event type (enough for most solo use cases)
- Calendar sync — Google Calendar, Outlook, Apple Calendar
- Booking page — customizable scheduling page
- REST API — full programmatic access
- Webhooks — real-time booking notifications
- Self-hosted — unlimited everything
Cal.com paid starts at $12/month for unlimited event types.
Quick Start (Self-Hosted)
git clone https://github.com/calcom/cal.com.git
cd cal.com
yarn install
yarn dev
# Access at http://localhost:3000
REST API
# Get your API key from Cal.com Settings > Developer > API Keys
# List event types
curl 'https://api.cal.com/v1/event-types?apiKey=YOUR_API_KEY'
# Get available slots
curl 'https://api.cal.com/v1/slots/available?apiKey=YOUR_API_KEY&eventTypeId=123&startTime=2026-04-01T00:00:00Z&endTime=2026-04-07T23:59:59Z'
# Create a booking
curl -X POST 'https://api.cal.com/v1/bookings?apiKey=YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"eventTypeId": 123,
"start": "2026-04-02T10:00:00Z",
"end": "2026-04-02T10:30:00Z",
"responses": {
"name": "Alice Johnson",
"email": "alice@example.com",
"notes": "Discussing project scope"
},
"timeZone": "America/New_York"
}'
# List bookings
curl 'https://api.cal.com/v1/bookings?apiKey=YOUR_API_KEY&status=upcoming'
# Cancel a booking
curl -X DELETE 'https://api.cal.com/v1/bookings/BOOKING_ID?apiKey=YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"cancellationReason": "Schedule conflict"}'
Embed in Your App
<!-- Inline embed -->
<script src="https://cal.com/embed.js"></script>
<cal-inline calLink="your-username/30min" />
<!-- Popup embed -->
<button onclick="Cal('ui', {styles: {branding: {brandColor: '#000'}}, calLink: 'your-username/30min'})">Book a Call</button>
Node.js Integration
const CAL_API = 'https://api.cal.com/v1';
const API_KEY = process.env.CAL_API_KEY;
async function getAvailableSlots(eventTypeId, startDate, endDate) {
const params = new URLSearchParams({
apiKey: API_KEY,
eventTypeId: String(eventTypeId),
startTime: startDate,
endTime: endDate
});
const res = await fetch(`${CAL_API}/slots/available?${params}`);
const { slots } = await res.json();
return slots;
}
async function createBooking(eventTypeId, start, end, attendee) {
const res = await fetch(`${CAL_API}/bookings?apiKey=${API_KEY}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
eventTypeId,
start,
end,
responses: attendee,
timeZone: attendee.timeZone || 'UTC'
})
});
return res.json();
}
// Get next available slot and book it
const slots = await getAvailableSlots(123, '2026-04-01', '2026-04-07');
if (Object.keys(slots).length > 0) {
const firstDay = Object.keys(slots)[0];
const firstSlot = slots[firstDay][0];
await createBooking(123, firstSlot.time, null, {
name: 'Alice',
email: 'alice@example.com'
});
}
Webhooks
// Receive booking notifications
app.post('/webhooks/cal', (req, res) => {
const { triggerEvent, payload } = req.body;
switch (triggerEvent) {
case 'BOOKING_CREATED':
console.log(`New booking: ${payload.attendees[0].name} at ${payload.startTime}`);
// Send Slack notification, add to CRM, etc.
break;
case 'BOOKING_CANCELLED':
console.log(`Cancelled: ${payload.uid}`);
break;
case 'BOOKING_RESCHEDULED':
console.log(`Rescheduled to: ${payload.startTime}`);
break;
}
res.sendStatus(200);
});
Use Cases
- Freelancers — client booking page
- SaaS — embed scheduling in onboarding flow
- Sales teams — prospect scheduling
- Support — customer support call booking
- Internal — interview scheduling, 1:1s
Need to automate your business? Check out my web scraping actors on Apify — data collection on autopilot.
Need scheduling integration? Email me at spinov001@gmail.com.
Top comments (0)