Skip to main content
Technology & EngineeringScheduling Services259 lines

Acuity Scheduling

Acuity Scheduling API integration for appointment booking, availability management, and calendar sync

Quick Summary25 lines
You are an expert in integrating Acuity Scheduling (by Squarespace) for appointment booking and calendar management.

## Key Points

- Always specify timezone when checking availability and creating appointments to avoid off-by-one day errors.
- Use appointment type IDs rather than names for reliability; names can be changed by account owners.
- Validate availability before booking — the API will reject appointments at unavailable times, but checking first provides a better user experience.
- Cache appointment types and calendar lists since they change infrequently; availability data should always be fetched fresh.
- Use webhook events instead of polling for appointment status changes.
- Acuity's API has rate limits (not publicly documented); implement exponential backoff on 429 responses to avoid disruption.
- The `datetime` field in appointment creation must match an available slot exactly — partial-hour offsets or arbitrary times will be rejected.
- Webhook payloads only contain the appointment ID and action, not the full appointment object; you must make a follow-up GET request to fetch details.
- OAuth tokens do not expire but can be revoked; always handle 401 responses gracefully by prompting re-authorization.
- Intake form fields use numeric IDs that differ per account; query `/appointment-types/{id}` to discover the correct field IDs before submitting.

## Quick Example

```bash
# Acuity uses HTTP Basic Auth: userId:apiKey
# Store credentials securely
export ACUITY_USER_ID="your-user-id"
export ACUITY_API_KEY="your-api-key"
```
skilldb get scheduling-services-skills/Acuity SchedulingFull skill: 259 lines
Paste into your CLAUDE.md or agent config

Acuity Scheduling — Scheduling Integration

You are an expert in integrating Acuity Scheduling (by Squarespace) for appointment booking and calendar management.

Core Philosophy

Overview

Acuity Scheduling is a client-facing appointment scheduling platform owned by Squarespace. It provides a REST API for managing appointments, availability, calendars, and clients programmatically. Acuity excels at service-based businesses where clients book specific appointment types with specific providers. The API uses HTTP Basic Auth with a user ID and API key, returning JSON responses.

Setup & Configuration

Authentication

# Acuity uses HTTP Basic Auth: userId:apiKey
# Store credentials securely
export ACUITY_USER_ID="your-user-id"
export ACUITY_API_KEY="your-api-key"

Basic Client Setup

import axios, { AxiosInstance } from "axios";

interface AcuityConfig {
  userId: string;
  apiKey: string;
  baseUrl?: string;
}

function createAcuityClient(config: AcuityConfig): AxiosInstance {
  const client = axios.create({
    baseURL: config.baseUrl || "https://acuityscheduling.com/api/v1",
    auth: {
      username: config.userId,
      password: config.apiKey,
    },
    headers: {
      "Content-Type": "application/json",
    },
  });

  return client;
}

const acuity = createAcuityClient({
  userId: process.env.ACUITY_USER_ID!,
  apiKey: process.env.ACUITY_API_KEY!,
});

OAuth2 Setup (for third-party apps)

import axios from "axios";

async function exchangeCodeForToken(
  code: string,
  clientId: string,
  clientSecret: string,
  redirectUri: string
): Promise<string> {
  const response = await axios.post(
    "https://acuityscheduling.com/oauth2/token",
    {
      grant_type: "authorization_code",
      code,
      client_id: clientId,
      client_secret: clientSecret,
      redirect_uri: redirectUri,
    }
  );

  return response.data.access_token;
}

Core Patterns

Fetching Appointment Types

interface AppointmentType {
  id: number;
  name: string;
  description: string;
  duration: number;
  price: string;
  category: string;
  color: string;
  private: boolean;
  calendarIDs: number[];
}

async function getAppointmentTypes(): Promise<AppointmentType[]> {
  const { data } = await acuity.get<AppointmentType[]>("/appointment-types");
  return data.filter((type) => !type.private);
}

Checking Availability

interface AvailabilitySlot {
  time: string; // ISO 8601
  slotsAvailable: number;
}

interface AvailabilityQuery {
  appointmentTypeID: number;
  date: string; // YYYY-MM-DD
  calendarID?: number;
  timezone?: string;
}

async function getAvailableDates(
  month: string,
  appointmentTypeID: number
): Promise<{ date: string }[]> {
  const { data } = await acuity.get("/availability/dates", {
    params: { month, appointmentTypeID },
  });
  return data;
}

async function getAvailableTimes(
  query: AvailabilityQuery
): Promise<AvailabilitySlot[]> {
  const { data } = await acuity.get<AvailabilitySlot[]>(
    "/availability/times",
    { params: query }
  );
  return data;
}

Creating an Appointment

interface CreateAppointmentPayload {
  datetime: string; // ISO 8601
  appointmentTypeID: number;
  firstName: string;
  lastName: string;
  email: string;
  phone?: string;
  timezone?: string;
  fields?: { id: number; value: string }[];
}

interface Appointment {
  id: number;
  datetime: string;
  endTime: string;
  type: string;
  firstName: string;
  lastName: string;
  email: string;
  confirmationPage: string;
  canceled: boolean;
}

async function bookAppointment(
  payload: CreateAppointmentPayload
): Promise<Appointment> {
  const { data } = await acuity.post<Appointment>("/appointments", payload);
  return data;
}

Canceling and Rescheduling

async function cancelAppointment(appointmentId: number): Promise<Appointment> {
  const { data } = await acuity.put<Appointment>(
    `/appointments/${appointmentId}/cancel`
  );
  return data;
}

async function rescheduleAppointment(
  appointmentId: number,
  newDatetime: string
): Promise<Appointment> {
  const { data } = await acuity.put<Appointment>(
    `/appointments/${appointmentId}/reschedule`,
    { datetime: newDatetime }
  );
  return data;
}

Webhook Handling

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

// Acuity sends webhooks for appointment lifecycle events
// Register webhooks at: Settings > Integrations > API > Webhooks

function setupWebhooks(app: express.Application): void {
  app.post("/webhooks/acuity", (req: Request, res: Response) => {
    const { action, id } = req.body;

    switch (action) {
      case "scheduled":
        handleNewAppointment(id);
        break;
      case "rescheduled":
        handleReschedule(id);
        break;
      case "canceled":
        handleCancellation(id);
        break;
      case "changed":
        handleUpdate(id);
        break;
    }

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

Best Practices

  • Always specify timezone when checking availability and creating appointments to avoid off-by-one day errors.
  • Use appointment type IDs rather than names for reliability; names can be changed by account owners.
  • Validate availability before booking — the API will reject appointments at unavailable times, but checking first provides a better user experience.
  • Cache appointment types and calendar lists since they change infrequently; availability data should always be fetched fresh.
  • Use webhook events instead of polling for appointment status changes.

Common Pitfalls

  • Acuity's API has rate limits (not publicly documented); implement exponential backoff on 429 responses to avoid disruption.
  • The datetime field in appointment creation must match an available slot exactly — partial-hour offsets or arbitrary times will be rejected.
  • Webhook payloads only contain the appointment ID and action, not the full appointment object; you must make a follow-up GET request to fetch details.
  • OAuth tokens do not expire but can be revoked; always handle 401 responses gracefully by prompting re-authorization.
  • Intake form fields use numeric IDs that differ per account; query /appointment-types/{id} to discover the correct field IDs before submitting.

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 →