Skip to main content
Technology & EngineeringScheduling Services245 lines

Savvycal

SavvyCal API integration for personalized scheduling links, availability overlays, and calendar coordination

Quick Summary23 lines
You are an expert in integrating SavvyCal for personalized scheduling and calendar coordination.

## Key Points

- Use prioritized availability to mark preferred meeting windows; SavvyCal shows these times first to invitees, nudging bookings toward times that work best for you.
- Set multiple duration options on a single link (e.g., 15min, 30min, 60min) so invitees can choose the appropriate meeting length.
- Implement webhook signature verification in production — never trust webhook payloads without cryptographic validation.
- Use the embed approach for in-app scheduling rather than redirecting users to a SavvyCal URL; this keeps users in your product flow.
- Filter events by date range using `from` and `to` parameters to avoid loading the entire event history.
- SavvyCal API tokens are account-scoped, not user-scoped; a single token accesses all links and events for the account. Rotate tokens if team members leave.
- The API paginates responses by default; check for a `next` cursor in the response metadata and loop through pages when fetching large event lists.
- Webhook retries occur on non-2xx responses; ensure your handler is idempotent to avoid processing the same event twice.
- Time zone handling relies on the invitee's reported time zone — always store and display times in UTC internally, converting only at the presentation layer.
- Canceling an event through the API does not send a notification email by default; check API parameters for notification options or handle notifications in your own application.

## Quick Example

```bash
# Generate a personal API token at https://savvycal.com/settings/api
export SAVVYCAL_API_TOKEN="your-api-token"
```
skilldb get scheduling-services-skills/SavvycalFull skill: 245 lines
Paste into your CLAUDE.md or agent config

SavvyCal — Scheduling Integration

You are an expert in integrating SavvyCal for personalized scheduling and calendar coordination.

Core Philosophy

Overview

SavvyCal is a scheduling platform that differentiates itself through recipient-first booking — invitees can overlay their own calendar on the scheduling page to find mutually convenient times. The API provides access to links, events, availability, and webhooks. SavvyCal is well-suited for teams that want a polished scheduling experience with prioritized time slots, round-robin assignment, and deep calendar integrations. The API uses bearer token authentication and returns JSON.

Setup & Configuration

API Token

# Generate a personal API token at https://savvycal.com/settings/api
export SAVVYCAL_API_TOKEN="your-api-token"

Client Setup

import axios, { AxiosInstance } from "axios";

function createSavvyCalClient(apiToken: string): AxiosInstance {
  return axios.create({
    baseURL: "https://api.savvycal.com/v1",
    headers: {
      Authorization: `Bearer ${apiToken}`,
      "Content-Type": "application/json",
      Accept: "application/json",
    },
  });
}

const savvycal = createSavvyCalClient(process.env.SAVVYCAL_API_TOKEN!);

Core Patterns

Managing Scheduling Links

interface SchedulingLink {
  id: string;
  name: string;
  slug: string;
  url: string;
  durations: number[]; // in minutes
  active: boolean;
  scheduling_url: string;
  time_zone: string;
}

async function listLinks(): Promise<SchedulingLink[]> {
  const { data } = await savvycal.get("/links");
  return data.data;
}

async function createLink(params: {
  name: string;
  slug: string;
  durations: number[];
  time_zone: string;
}): Promise<SchedulingLink> {
  const { data } = await savvycal.post("/links", params);
  return data.data;
}

Retrieving Events (Booked Meetings)

interface SavvyCalEvent {
  id: string;
  name: string;
  start_at: string; // ISO 8601
  end_at: string;
  status: "confirmed" | "canceled" | "pending";
  invitee: {
    name: string;
    email: string;
    time_zone: string;
  };
  link: {
    id: string;
    name: string;
  };
  location: {
    type: string;
    value: string; // e.g., Zoom URL or physical address
  };
}

async function listEvents(params?: {
  status?: string;
  from?: string;
  to?: string;
}): Promise<SavvyCalEvent[]> {
  const { data } = await savvycal.get("/events", { params });
  return data.data;
}

async function getEvent(eventId: string): Promise<SavvyCalEvent> {
  const { data } = await savvycal.get(`/events/${eventId}`);
  return data.data;
}

Canceling Events

async function cancelEvent(
  eventId: string,
  reason?: string
): Promise<SavvyCalEvent> {
  const { data } = await savvycal.post(`/events/${eventId}/cancel`, {
    reason,
  });
  return data.data;
}

Webhook Integration

import express, { Request, Response } from "express";
import crypto from "crypto";

// Register webhooks at https://savvycal.com/settings/api/webhooks
// Events: event.created, event.canceled, event.rescheduled

interface SavvyCalWebhookPayload {
  event: string; // e.g., "event.created"
  data: SavvyCalEvent;
}

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const computed = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(computed)
  );
}

function setupSavvyCalWebhooks(app: express.Application): void {
  app.post("/webhooks/savvycal", (req: Request, res: Response) => {
    const signature = req.headers["x-savvycal-signature"] as string;
    const rawBody = JSON.stringify(req.body);

    if (
      !verifyWebhookSignature(
        rawBody,
        signature,
        process.env.SAVVYCAL_WEBHOOK_SECRET!
      )
    ) {
      res.status(401).send("Invalid signature");
      return;
    }

    const payload: SavvyCalWebhookPayload = req.body;

    switch (payload.event) {
      case "event.created":
        handleNewBooking(payload.data);
        break;
      case "event.canceled":
        handleCancellation(payload.data);
        break;
      case "event.rescheduled":
        handleReschedule(payload.data);
        break;
    }

    res.status(200).send("OK");
  });
}

Generating Embeddable Scheduling

// SavvyCal provides a JavaScript embed for inline scheduling
function generateEmbedSnippet(linkSlug: string, displayName: string): string {
  return `
<script>
  window.SavvyCal = window.SavvyCal || function() {
    (SavvyCal.q = SavvyCal.q || []).push(arguments);
  };
</script>
<script async src="https://embed.savvycal.com/v1/embed.js"></script>
<script>
  SavvyCal('init');
  SavvyCal('inline', {
    link: '${displayName}/${linkSlug}',
    selector: '#savvycal-embed',
    theme: 'light'
  });
</script>
<div id="savvycal-embed"></div>
  `.trim();
}

Best Practices

  • Use prioritized availability to mark preferred meeting windows; SavvyCal shows these times first to invitees, nudging bookings toward times that work best for you.
  • Set multiple duration options on a single link (e.g., 15min, 30min, 60min) so invitees can choose the appropriate meeting length.
  • Implement webhook signature verification in production — never trust webhook payloads without cryptographic validation.
  • Use the embed approach for in-app scheduling rather than redirecting users to a SavvyCal URL; this keeps users in your product flow.
  • Filter events by date range using from and to parameters to avoid loading the entire event history.

Common Pitfalls

  • SavvyCal API tokens are account-scoped, not user-scoped; a single token accesses all links and events for the account. Rotate tokens if team members leave.
  • The API paginates responses by default; check for a next cursor in the response metadata and loop through pages when fetching large event lists.
  • Webhook retries occur on non-2xx responses; ensure your handler is idempotent to avoid processing the same event twice.
  • Time zone handling relies on the invitee's reported time zone — always store and display times in UTC internally, converting only at the presentation layer.
  • Canceling an event through the API does not send a notification email by default; check API parameters for notification options or handle notifications in your own application.

Anti-Patterns

Using the service without understanding its pricing model. Cloud services bill differently — per request, per GB, per seat. Deploying without modeling expected costs leads to surprise invoices.

Hardcoding configuration instead of using environment variables. API keys, endpoints, and feature flags change between environments. Hardcoded values break deployments and leak secrets.

Ignoring the service's rate limits and quotas. Every external API has throughput limits. Failing to implement backoff, queuing, or caching results in dropped requests under load.

Treating the service as always available. External services go down. Without circuit breakers, fallbacks, or graceful degradation, a third-party outage becomes your outage.

Coupling your architecture to a single provider's API. Building directly against provider-specific interfaces makes migration painful. Wrap external services in thin adapter layers.

Install this skill directly: skilldb add scheduling-services-skills

Get CLI access →