Calendars API
Manages calendar connections for availability checking and event creation.
Base Path: /api/calendars
Authentication: All endpoints require Bearer token.
Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
GET | / | Yes | List calendar connections |
GET | /:id | Yes | Get single calendar connection |
GET | /:id/list | Yes | List calendars in a connection |
PUT | /:id/preferences | Yes | Update calendar preferences |
DELETE | /:id | Yes | Disconnect calendar account |
List Calendar Connections
Retrieves all calendar accounts connected to the user, ordered by creation date (oldest first).
GET /api/calendarsResponse:
{
"success": true,
"data": [
{
"id": "uuid",
"provider": "google",
"provider_account_email": "[email protected]",
"calendars_for_availability": ["primary", "[email protected]"],
"calendar_for_events": "primary",
"is_valid": true,
"error_message": null,
"created_at": "2025-01-07T10:00:00Z",
"updated_at": "2025-01-07T10:00:00Z"
}
]
}Note: Token values are never returned in API responses. The refresh_token is excluded.
Get Single Calendar Connection
Retrieves details for a specific calendar connection.
GET /api/calendars/:idResponse:
{
"success": true,
"data": {
"id": "uuid",
"user_id": "uuid",
"provider": "google",
"provider_account_id": "google-user-id",
"provider_account_email": "[email protected]",
"calendars_for_availability": ["primary"],
"calendar_for_events": "primary",
"is_valid": true,
"error_message": null,
"last_validated_at": "2025-01-07T10:00:00Z",
"created_at": "2025-01-07T10:00:00Z",
"updated_at": "2025-01-07T10:00:00Z"
}
}Note: The refresh_token field is explicitly removed from the response.
List Calendars in Connection
Retrieves all calendars available in a connected Google account.
GET /api/calendars/:id/listResponse:
{
"success": true,
"data": [
{
"id": "primary",
"summary": "John Host",
"description": null,
"timeZone": "America/New_York",
"backgroundColor": "#9fe1e7",
"foregroundColor": "#000000",
"accessRole": "owner",
"primary": true,
"selectedForAvailability": true,
"selectedForEvents": true
},
{
"id": "[email protected]",
"summary": "Work Calendar",
"description": "Team meetings and deadlines",
"timeZone": "America/New_York",
"backgroundColor": "#16a765",
"foregroundColor": "#ffffff",
"accessRole": "writer",
"primary": false,
"selectedForAvailability": true,
"selectedForEvents": false
}
]
}Side Effects:
- Refreshes OAuth token if expired
- Queries Google Calendar API for calendar list
- Marks connection as
is_valid: falseif token refresh fails
Error Responses:
400- Connection is invalid (needs reconnection)404- Connection not found500- Google OAuth not configured
Update Calendar Preferences
Updates which calendars to check for conflicts and which to write events to.
PUT /api/calendars/:id/preferencesRequest Body (Zod validated):
{
"calendars_for_availability": ["primary", "[email protected]"],
"calendar_for_events": "primary"
}Fields:
| Field | Type | Description |
|---|---|---|
calendars_for_availability | string[] | Calendar IDs to check for conflicts |
calendar_for_events | string | null | Calendar ID to create events in |
Response:
{
"success": true,
"data": {
"id": "uuid",
"provider": "google",
"provider_account_email": "[email protected]",
"calendars_for_availability": ["primary", "[email protected]"],
"calendar_for_events": "primary",
"is_valid": true,
"updated_at": "2025-01-07T11:00:00Z"
}
}Disconnect Calendar
Removes a calendar connection and revokes OAuth tokens.
DELETE /api/calendars/:idResponse:
{
"success": true,
"message": "Calendar disconnected"
}Side Effects:
- Attempts to revoke Google OAuth tokens (continues even if revocation fails)
- Deletes connection record from database
- Does not affect existing calendar events
Error Responses:
401- Unauthorized (missing or invalid token)404- Connection not found or belongs to different user500- Database deletion failed
Data Model
Database Table: calendar_connections
interface CalendarConnection {
id: string; // UUID primary key
user_id: string; // References auth.users(id)
provider: 'google'; // Currently only Google supported
provider_account_id: string; // Google account ID
provider_account_email: string; // Display email
refresh_token: string; // Never exposed in API responses
calendars_for_availability: string[]; // JSONB - Calendar IDs for conflict checking
calendar_for_events: string | null; // Calendar ID for event creation
is_valid: boolean; // False if token refresh fails
error_message: string | null; // Error details if invalid
last_validated_at: string; // Last successful token use
created_at: string;
updated_at: string;
}Column Notes
| Column | Purpose |
|---|---|
calendars_for_availability | JSONB array of calendar IDs to check during availability calculations |
calendar_for_events | Single calendar ID where confirmed bookings create events |
is_valid | Set to false when token refresh fails; user must reconnect |
error_message | Human-readable error message when is_valid is false |
Calendar Selection
Calendars for Availability (Read)
These calendars are checked when calculating availability:
// During availability check
const busyTimes = await getBusyTimes(
accessToken,
connection.calendars_for_availability, // Check all selected
startTime,
endTime
);- Recommendation: Select all calendars that might contain conflicts
- Example: Personal + Work + Shared team calendars
Calendar for Events (Write)
Events are created in this calendar when bookings are confirmed:
// During booking confirmation
await createCalendarEvent(
accessToken,
connection.calendar_for_events, // Write to this calendar
eventDetails
);- Recommendation: Use the primary or most relevant calendar
- Note: Only one write calendar per connection
Token Refresh Flow
OAuth tokens are automatically refreshed when performing calendar operations:
// src/api/routes/calendars/index.ts
async function getAccessToken(connection, supabase, clientId, clientSecret) {
try {
const { access_token } = await refreshAccessToken(
connection.refresh_token,
clientId,
clientSecret
);
return access_token;
} catch (error) {
// Mark connection as invalid if refresh fails
await supabase
.from('calendar_connections')
.update({
is_valid: false,
error_message: 'Failed to refresh access token. Please reconnect.'
})
.eq('id', connection.id);
throw new Error('Calendar connection is no longer valid. Please reconnect.');
}
}Multiple Connections
Users can connect multiple Google accounts:
{
"success": true,
"data": [
{
"id": "uuid-1",
"provider_account_email": "[email protected]",
"created_at": "2025-01-05T10:00:00Z"
},
{
"id": "uuid-2",
"provider_account_email": "[email protected]",
"created_at": "2025-01-07T10:00:00Z"
}
]
}Behavior:
- All connections' selected calendars are checked for availability conflicts
- Events are created on the first connection (by
created_atascending) that has acalendar_for_eventsset - Connections are returned ordered by creation date (oldest first)
TypeScript Client Usage
import { createApiClient } from '$api/client';
const client = createApiClient(fetch);
const headers = { Authorization: `Bearer ${token}` };
// List connections
const connectionsRes = await client.api.calendars.$get({}, { headers });
// Get single connection
const connectionRes = await client.api.calendars[':id'].$get(
{ param: { id: 'connection-uuid' } },
{ headers }
);
// List calendars in connection
const calendarsRes = await client.api.calendars[':id'].list.$get(
{ param: { id: 'connection-uuid' } },
{ headers }
);
// Update preferences
const updateRes = await client.api.calendars[':id'].preferences.$put(
{
param: { id: 'connection-uuid' },
json: {
calendars_for_availability: ['primary', '[email protected]'],
calendar_for_events: 'primary'
}
},
{ headers }
);
// Disconnect
const deleteRes = await client.api.calendars[':id'].$delete(
{ param: { id: 'connection-uuid' } },
{ headers }
);Related
- Google OAuth API - OAuth connection flow
- Availability API - Using calendars for conflicts
- Bookings API - Event creation on confirmation
- Google Calendar Integration - Full integration docs