Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Expert guidance for Cal.com, the open-source scheduling platform for building booking and appointment systems. Helps developers integrate Cal.com's embed widgets, REST API, and webhooks to add scheduling capabilities to their applications.
.claude/skills/terminalskills-cal-com/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 57% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 72% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 55% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 53% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 96% | 0% |
Cal.com, the open-source scheduling platform for building booking and appointment systems. Helps developers integrate Cal.com's embed widgets, REST API, and webhooks to add scheduling capabilities to their applications.
Add a booking widget to any website:
tsx// src/components/BookingWidget.tsx — Embed Cal.com scheduling in a React app import Cal, { getCalApi } from "@calcom/embed-react"; import { useEffect } from "react"; export function BookingWidget() { useEffect(() => { (async () => { const cal = await getCalApi(); // Configure the embed appearance cal("ui", { theme: "light", styles: { branding: { brandColor: "#6366f1" } }, hideEventTypeDetails: false, layout: "month_view", // "month_view" | "week_view" | "column_view" }); })(); }, []); return ( <Cal calLink="your-team/discovery-call" // Your Cal.com event link style={{ width: "100%", height: "100%", overflow: "scroll" }} config={{ layout: "month_view", name: "Customer Name", // Pre-fill guest name email: "customer@example.com", // Pre-fill guest email notes: "Interested in Enterprise plan", }} /> ); } // Floating button that opens calendar in a popup export function BookingButton() { useEffect(() => { (async () => { const cal = await getCalApi(); cal("floatingButton", { calLink: "your-team/discovery-call", buttonText: "Book a Demo", buttonColor: "#6366f1", buttonTextColor: "#ffffff", buttonPosition: "bottom-right", }); })(); }, []); return null; // The floating button renders itself }
Manage bookings, event types, and availability programmatically:
typescript// src/cal/client.ts — Cal.com API client for server-side operations const CAL_API_URL = "https://api.cal.com/v2"; const CAL_API_KEY = process.env.CAL_API_KEY!; async function calFetch(path: string, options?: RequestInit) { const response = await fetch(`${CAL_API_URL}${path}`, { ...options, headers: { "Content-Type": "application/json", "cal-api-version": "2024-08-13", // Pin API version for stability Authorization: `Bearer ${CAL_API_KEY}`, ...options?.headers, }, }); if (!response.ok) { const error = await response.json(); throw new Error(`Cal.com API error: ${error.message}`); } return response.json(); } // Create a new event type (e.g., "30-min Discovery Call") async function createEventType(params: { title: string; slug: string; lengthInMinutes: number; description?: string; locations?: { type: string; link?: string }[]; }) { return calFetch("/event-types", { method: "POST", body: JSON.stringify({ title: params.title, slug: params.slug, lengthInMinutes: params.lengthInMinutes, description: params.description, locations: params.locations ?? [ { type: "integrations:google:meet" }, // Default: Google Meet ], bookingFields: [ { name: "company", type: "text", label: "Company Name", required: true }, { name: "teamSize", type: "select", label: "Team Size", options: ["1-10", "11-50", "51-200", "200+"], required: true }, ], }), }); } // Get available time slots for a date range async function getAvailability(params: { eventTypeId: number; startTime: string; // ISO 8601 endTime: string; timeZone: string; }) { const query = new URLSearchParams({ eventTypeId: String(params.eventTypeId), startTime: params.startTime, endTime: params.endTime, timeZone: params.timeZone, }); return calFetch(`/slots?${query}`); } // Create a booking async function createBooking(params: { eventTypeId: number; start: string; // ISO 8601 datetime attendee: { name: string; email: string; timeZone: string }; metadata?: Record<string, string>; }) { return calFetch("/bookings", { method: "POST", body: JSON.stringify({ eventTypeId: params.eventTypeId, start: params.start, attendee: params.attendee, metadata: params.metadata, language: "en", }), }); } // Cancel a booking async function cancelBooking(bookingUid: string, reason?: string) { return calFetch(`/bookings/${bookingUid}/cancel`, { method: "POST", body: JSON.stringify({ reason }), }); } // Reschedule a booking async function rescheduleBooking(bookingUid: string, newStart: string) { return calFetch(`/bookings/${bookingUid}/reschedule`, { method: "POST", body: JSON.stringify({ start: newStart }), }); }
React to booking events in real time:
typescript// app/api/cal-webhook/route.ts — Handle Cal.com webhook events import { NextRequest, NextResponse } from "next/server"; import crypto from "crypto"; const WEBHOOK_SECRET = process.env.CAL_WEBHOOK_SECRET!; // Verify the webhook signature to ensure it's from Cal.com function verifySignature(payload: string, signature: string): boolean { const expected = crypto .createHmac("sha256", WEBHOOK_SECRET) .update(payload) .digest("hex"); return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); } export async function POST(request: NextRequest) { const payload = await request.text(); const signature = request.headers.get("x-cal-signature-256") ?? ""; if (!verifySignature(payload, signature)) { return NextResponse.json({ error: "Invalid signature" }, { status: 401 }); } const event = JSON.parse(payload); switch (event.triggerEvent) { case "BOOKING_CREATED": // New booking — send confirmation, update CRM, notify sales await handleNewBooking({ bookingId: event.payload.bookingId, attendeeName: event.payload.attendees[0]?.name, attendeeEmail: event.payload.attendees[0]?.email, startTime: event.payload.startTime, eventType: event.payload.eventTitle, metadata: event.payload.metadata, }); break; case "BOOKING_CANCELLED": // Booking cancelled — update CRM, free up resources await handleCancellation({ bookingId: event.payload.bookingId, reason: event.payload.cancellationReason, }); break; case "BOOKING_RESCHEDULED": // Booking moved — update calendar integrations await handleReschedule({ bookingId: event.payload.bookingId, oldTime: event.payload.previousStartTime, newTime: event.payload.startTime, }); break; case "MEETING_ENDED": // Meeting finished — trigger follow-up sequence await triggerFollowUp(event.payload.bookingId); break; } return NextResponse.json({ received: true }); } async function handleNewBooking(booking: any) { // Example: Create a deal in your CRM await crm.createDeal({ name: `Demo: ${booking.attendeeName}`, contact: booking.attendeeEmail, stage: "discovery", source: "website-booking", metadata: booking.metadata, }); // Notify the sales team via Slack await slack.postMessage({ channel: "#sales-notifications", text: `📅 New ${booking.eventType} booked!\n👤 ${booking.attendeeName} (${booking.attendeeEmail})\n🕐 ${new Date(booking.startTime).toLocaleString()}`, }); }
Configure round-robin scheduling and team calendars:
typescript// src/cal/team-setup.ts — Configure team scheduling rules async function setupTeamScheduling() { // Create a team event type with round-robin assignment const eventType = await calFetch("/event-types", { method: "POST", body: JSON.stringify({ title: "Product Demo", slug: "product-demo", lengthInMinutes: 45, schedulingType: "ROUND_ROBIN", // Rotate among team members hosts: [ { userId: 101, isFixed: false }, // Participates in rotation { userId: 102, isFixed: false }, { userId: 103, isFixed: true }, // Always included (e.g., sales engineer) ], beforeEventBuffer: 15, // 15-min buffer before meetings afterEventBuffer: 10, // 10-min buffer after minimumBookingNotice: 120, // Minimum 2 hours notice slotInterval: 15, // Show slots every 15 minutes locations: [ { type: "integrations:zoom" }, // Auto-create Zoom meeting ], }), }); // Set working hours for the team await calFetch("/schedules", { method: "POST", body: JSON.stringify({ name: "Business Hours", timeZone: "America/New_York", isDefault: true, availability: [ // Monday-Friday 9 AM to 5 PM { days: [1, 2, 3, 4, 5], startTime: "09:00", endTime: "17:00" }, ], // Block specific dates (holidays, company events) dateOverrides: [ { date: "2026-12-25", startTime: null, endTime: null }, // Christmas — unavailable { date: "2026-12-31", startTime: "09:00", endTime: "12:00" }, // NYE — morning only ], }), }); }
bash# React embed npm install @calcom/embed-react # For self-hosted Cal.com (Docker) git clone https://github.com/calcom/cal.com.git cd cal.com cp .env.example .env docker compose up -d
User request:
I just installed Cal Com. Help me configure it for my TypeScript + React workflow with my preferred keybindings.The agent creates the configuration file with TypeScript-aware settings, configures relevant plugins/extensions for React development, sets up keyboard shortcuts matching the user's preferences, and verifies the setup works correctly.
User request:
I want to add a custom rest api integration to Cal Com. How do I build one?The agent scaffolds the extension/plugin project, implements the core functionality following Cal Com's API patterns, adds configuration options, and provides testing instructions to verify it works end-to-end.
cal-api-version header to avoid breaking changes when Cal.com updatesx-cal-signature-256 to prevent spoofed eventsbeforeEventBuffer and afterEventBuffer to prevent back-to-back meetings| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | pass→pass | 7,670 | 4,161 | -46% | 1 | 1 | 0% | 1,839 | 3,985 | +117% | 0 | 0 | — |
case-04 | fail→pass | 12,270 | 6,237 | -49% | 1 | 1 | 0% | 2,880 | 4,510 | +57% | 0 | 0 | — |
case-11 | fail→pass | 12,842 | 6,999 | -45% | 1 | 1 | 0% | 2,549 | 4,375 | +72% | 0 | 0 | — |
case-01 | fail→pass | 17,852 | 14,363 | -20% | 1 | 1 | 0% | 4,370 | 6,762 | +55% | 0 | 0 | — |
case-02 | fail→pass | 16,018 | 13,352 | -17% | 1 | 1 | 0% | 3,808 | 5,836 | +53% | 0 | 0 | — |
case-05 | pass→pass | 9,879 | 8,907 | -10% | 1 | 1 | 0% | 2,390 | 5,166 | +116% | 0 | 0 | — |
case-06 | fail→pass | 17,558 | 14,481 | -18% | 1 | 1 | 0% | 3,289 | 6,438 | +96% | 0 | 0 | — |
case-07 | pass→pass | 10,375 | 5,686 | -45% | 1 | 1 | 0% | 2,450 | 4,417 | +80% | 0 | 0 | — |
case-08 | pass→pass | 12,934 | 6,654 | -49% | 1 | 1 | 0% | 2,832 | 4,492 | +59% | 0 | 0 | — |
case-09 | pass→pass | 12,875 | 6,878 | -47% | 1 | 1 | 0% | 2,881 | 4,696 | +63% | 0 | 0 | — |
case-10 | pass→pass | 10,609 | 5,943 | -44% | 1 | 1 | 0% | 2,253 | 4,402 | +95% | 0 | 0 | — |
case-12 | pass→pass | 10,041 | 5,518 | -45% | 1 | 1 | 0% | 2,244 | 4,338 | +93% | 0 | 0 | — |
case-13 | fail→pass | 11,702 | 7,383 | -37% | 1 | 1 | 0% | 2,725 | 4,759 | +75% | 0 | 0 | — |
case-14 | pass→pass | 12,257 | 8,475 | -31% | 1 | 1 | 0% | 1,864 | 4,195 | +125% | 0 | 0 | — |
case-15 | fail→pass | 9,468 | 4,674 | -51% | 1 | 1 | 0% | 1,830 | 3,952 | +116% | 0 | 0 | — |
case-16 | fail→pass | 10,015 | 8,017 | -20% | 1 | 1 | 0% | 2,097 | 4,932 | +135% | 0 | 0 | — |
case-21 | fail→fail | 13,713 | 13,656 | -0% | 1 | 1 | 0% | 2,944 | 5,812 | +97% | 0 | 0 | — |
case-17 | fail→pass | 13,460 | 12,769 | -5% | 1 | 1 | 0% | 2,996 | 5,355 | +79% | 0 | 0 | — |
case-18 | pass→pass | 8,194 | 4,816 | -41% | 1 | 1 | 0% | 1,625 | 4,017 | +147% | 0 | 0 | — |
case-19 | pass→pass | 18,881 | 10,887 | -42% | 1 | 1 | 0% | 4,100 | 5,610 | +37% | 0 | 0 | — |
case-20 | pass→pass | 9,996 | 7,502 | -25% | 1 | 1 | 0% | 2,172 | 4,826 | +122% | 0 | 0 | — |
case-22 | pass→pass | 12,091 | 16,122 | +33% | 1 | 1 | 0% | 2,555 | 6,244 | +144% | 0 | 0 | — |
case-23 | pass→pass | 17,458 | 13,422 | -23% | 1 | 1 | 0% | 3,145 | 6,208 | +97% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +39 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.