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
- Busy-time blocking -- When checking availability, Astrocal reads your connected calendars and removes any slots that overlap with existing events.
- 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.
- 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=googleThe 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=microsoftConnecting 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
| Preset | Server URL | Notes |
|---|---|---|
apple | Auto-configured | Requires an Apple app-specific password |
fastmail | Auto-configured | Requires a Fastmail app password |
nextcloud | Requires server_url | e.g., https://cloud.example.com/remote.php/dav |
custom | Requires server_url | Any 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
| Field | Values | Description |
|---|---|---|
status | active, needs_reauth | Whether the connection is usable |
sync_status | connected, expired, error | Current sync health |
sync_error | string or null | Error message if sync has failed |
last_sync_at | string or null | Last 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
| Status | Error Code | Description |
|---|---|---|
| 400 | validation_error | Missing or invalid fields (e.g., no server_url for nextcloud preset) |
| 401 | unauthorized | Invalid API key |
| 404 | not_found | Calendar connection does not exist |
| 409 | conflict | Calendar account is already connected |
| 502 | provider_error | Calendar 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