Astrocal
Guides

Calendars

Connect Google, Microsoft, and CalDAV calendars for automatic busy-time blocking.

Calendar connections let Astrocal read your existing events and block busy times from availability. When a booking is created, cancelled, or rescheduled, Astrocal automatically syncs the calendar event.

Prerequisites

  • An Astrocal account with an API key (Authentication guide)
  • For Google: a Google account with Google Calendar
  • For Microsoft: a Microsoft 365 or Outlook.com account
  • For CalDAV: an app-specific password from your provider (Apple, Fastmail, Nextcloud, or any CalDAV server)

How calendar sync works

  1. Busy-time blocking -- When checking availability, Astrocal reads your connected calendars and removes any slots that overlap with existing events.
  2. Booking sync -- When a booking is created, a calendar event is automatically added. When a booking is cancelled or rescheduled, the calendar event is updated or removed.
  3. One-way write -- Astrocal is the source of truth. It writes booking events to your calendar, but external calendar changes are not synced back to Astrocal.

Connecting Google Calendar

Google Calendar uses an OAuth redirect flow. Your application initiates the connection, the user authorizes access in Google, and Astrocal handles the token exchange.

Step 1: Initiate the OAuth flow

curl "https://api.astrocal.dev/v1/calendars/google/connect?redirect_uri=https://yourapp.com/calendar-callback" \
  -H "Authorization: Bearer YOUR_API_KEY"
const response = await fetch(
  "https://api.astrocal.dev/v1/calendars/google/connect?redirect_uri=https://yourapp.com/calendar-callback",
  {
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
    },
  }
);
const data = await response.json();

Try it in the API playground →

Response:

{
  "authorization_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&scope=...&redirect_uri=..."
}

Step 2: Redirect the user

Open the authorization_url in the user's browser. They will see Google's consent screen requesting calendar access.

Step 3: Handle the callback

After the user authorizes, Google redirects back to your redirect_uri. Astrocal handles the token exchange automatically and redirects the user to your callback URL with a success indicator:

https://yourapp.com/calendar-callback?connected=true&provider=google

The calendar connection is now active and will be used for busy-time blocking and booking sync.

Connecting Microsoft Calendar

Microsoft Calendar also uses an OAuth redirect flow with PKCE (Proof Key for Code Exchange).

Step 1: Initiate the OAuth flow

curl "https://api.astrocal.dev/v1/calendars/microsoft/connect?redirect_uri=https://yourapp.com/calendar-callback" \
  -H "Authorization: Bearer YOUR_API_KEY"
const response = await fetch(
  "https://api.astrocal.dev/v1/calendars/microsoft/connect?redirect_uri=https://yourapp.com/calendar-callback",
  {
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
    },
  }
);
const data = await response.json();

Try it in the API playground →

Response:

{
  "authorization_url": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=...&scope=..."
}

Step 2: Redirect the user

Open the authorization_url in the user's browser. They will see Microsoft's consent screen.

Step 3: Handle the callback

After authorization, Microsoft redirects back to your redirect_uri. Astrocal handles the token exchange and redirects the user:

https://yourapp.com/calendar-callback?connected=true&provider=microsoft

Connecting CalDAV calendars

CalDAV connections use a username and password instead of OAuth. This supports Apple iCloud Calendar, Fastmail, Nextcloud, and any standard CalDAV server.

CalDAV providers require an app-specific password, not your regular account password. See your provider's documentation for how to generate one.

Supported presets

PresetServer URLNotes
appleAuto-configuredRequires an Apple app-specific password
fastmailAuto-configuredRequires a Fastmail app password
nextcloudRequires server_urle.g., https://cloud.example.com/remote.php/dav
customRequires server_urlAny CalDAV-compatible server

Connecting with a preset

Apple iCloud Calendar:

curl -X POST https://api.astrocal.dev/v1/calendars/caldav/connect \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "preset": "apple",
    "username": "user@icloud.com",
    "password": "xxxx-xxxx-xxxx-xxxx"
  }'
const response = await fetch(
  "https://api.astrocal.dev/v1/calendars/caldav/connect",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      preset: "apple",
      username: "user@icloud.com",
      password: "xxxx-xxxx-xxxx-xxxx",
    }),
  }
);
const data = await response.json();

Fastmail:

curl -X POST https://api.astrocal.dev/v1/calendars/caldav/connect \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "preset": "fastmail",
    "username": "user@fastmail.com",
    "password": "your-app-password"
  }'
const response = await fetch(
  "https://api.astrocal.dev/v1/calendars/caldav/connect",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      preset: "fastmail",
      username: "user@fastmail.com",
      password: "your-app-password",
    }),
  }
);
const data = await response.json();

Nextcloud (requires server_url):

curl -X POST https://api.astrocal.dev/v1/calendars/caldav/connect \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "preset": "nextcloud",
    "username": "admin",
    "password": "your-app-password",
    "server_url": "https://cloud.example.com/remote.php/dav"
  }'
const response = await fetch(
  "https://api.astrocal.dev/v1/calendars/caldav/connect",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      preset: "nextcloud",
      username: "admin",
      password: "your-app-password",
      server_url: "https://cloud.example.com/remote.php/dav",
    }),
  }
);
const data = await response.json();

Custom CalDAV server:

curl -X POST https://api.astrocal.dev/v1/calendars/caldav/connect \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "preset": "custom",
    "username": "user",
    "password": "password",
    "server_url": "https://caldav.example.com/dav"
  }'
const response = await fetch(
  "https://api.astrocal.dev/v1/calendars/caldav/connect",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      preset: "custom",
      username: "user",
      password: "password",
      server_url: "https://caldav.example.com/dav",
    }),
  }
);
const data = await response.json();

Try it in the API playground →

Response (201 Created):

{
  "id": "cal_abc123",
  "provider": "caldav",
  "calendar_id": "user@icloud.com",
  "account_email": "user@icloud.com",
  "connected_at": "2026-03-15T10:00:00Z",
  "status": "active",
  "last_sync_at": null,
  "sync_status": "connected",
  "sync_error": null
}

Listing connections

List all calendar connections for your organization:

curl https://api.astrocal.dev/v1/calendars \
  -H "Authorization: Bearer YOUR_API_KEY"
const response = await fetch("https://api.astrocal.dev/v1/calendars", {
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
  },
});
const data = await response.json();

Try it in the API playground →

Response:

{
  "data": [
    {
      "id": "cal_abc123",
      "provider": "google",
      "calendar_id": "primary",
      "account_email": "user@gmail.com",
      "connected_at": "2026-03-01T10:00:00Z",
      "status": "active",
      "last_sync_at": "2026-03-15T14:30:00Z",
      "sync_status": "connected",
      "sync_error": null
    }
  ],
  "has_more": false
}

Connection status fields

FieldValuesDescription
statusactive, needs_reauthWhether the connection is usable
sync_statusconnected, expired, errorCurrent sync health
sync_errorstring or nullError message if sync has failed
last_sync_atstring or nullLast successful sync timestamp

A needs_reauth status means the OAuth tokens have expired and the user needs to reconnect. For CalDAV, this typically means the app-specific password was revoked.

Disconnecting a calendar

curl -X DELETE https://api.astrocal.dev/v1/calendars/cal_abc123 \
  -H "Authorization: Bearer YOUR_API_KEY"
const response = await fetch(
  "https://api.astrocal.dev/v1/calendars/cal_abc123",
  {
    method: "DELETE",
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
    },
  }
);

Try it in the API playground →

Response:

{
  "id": "cal_abc123",
  "deleted": true
}

After disconnecting, busy-time blocking stops immediately. Existing booking events that were already written to the calendar remain (they are not deleted).

Error handling

StatusError CodeDescription
400validation_errorMissing or invalid fields (e.g., no server_url for nextcloud preset)
401unauthorizedInvalid API key
404not_foundCalendar connection does not exist
409conflictCalendar account is already connected
502provider_errorCalendar provider returned an error (e.g., invalid credentials for CalDAV)

Next steps

  • Availability -- Configure availability rules and query bookable slots
  • Bookings -- Create bookings that automatically sync to connected calendars
  • Webhooks -- Get notified when bookings are created, cancelled, or rescheduled
  • API Reference -- Full endpoint documentation

On this page