Availability API
Calculates available time slots based on booking link configuration, existing bookings, and calendar busy times.
Base Path: /api/availability
Authentication: Public endpoints (used by guest booking portal).
Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
GET | / | No | Get available slots for a date |
GET | /month | No | Get available dates in a month |
Get Available Slots
Returns available time slots for a specific date.
GET /api/availability?bookingLinkId={id}&date={date}&timezone={tz}Query Parameters (Zod validated):
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
bookingLinkId | UUID | Yes | - | Booking link to check |
date | YYYY-MM-DD | Yes | - | Date to check availability |
timezone | string | No | UTC | Guest's timezone (IANA format) |
Success Response:
{
"success": true,
"data": {
"date": "2025-01-15",
"timezone": "America/New_York",
"bookingLinkTimezone": "America/Los_Angeles",
"bookingLink": {
"id": "uuid",
"name": "Guest Interview",
"duration_minutes": 60,
"description": "Standard interview slot",
"timezone": "America/Los_Angeles"
},
"podcast": {
"id": "uuid",
"slug": "my-podcast",
"title": "My Podcast"
},
"slots": [
{
"start": "2025-01-15T14:00:00.000Z",
"end": "2025-01-15T15:00:00.000Z",
"available": true
},
{
"start": "2025-01-15T16:15:00.000Z",
"end": "2025-01-15T17:15:00.000Z",
"available": true
}
]
}
}Empty Slots Response (date constraints or no rules):
{
"success": true,
"data": {
"date": "2025-01-15",
"slots": [],
"message": "Bookings require 24 hours notice"
}
}Slot Calculation Process
Filtering Steps
- Date Constraints: Validate against
min_notice_hoursandmax_advance_days - Day of Week Rules: Check if
availability_rulesexist for this day - Generate Slots: Create potential slots based on duration and time windows
- Past Time Filter: Remove slots that have already passed
- Min Notice Filter: Remove slots within
min_notice_hoursof now - Calendar Conflicts: Remove slots overlapping Google Calendar busy periods (with buffers)
- Booking Conflicts: Remove slots overlapping existing
pendingorconfirmedbookings (with buffers)
Get Available Dates
Returns dates with availability in a given month (for calendar view).
GET /api/availability/month?bookingLinkId={id}&year={year}&month={month}&timezone={tz}Query Parameters (Zod validated):
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
bookingLinkId | UUID | Yes | - | Booking link to check |
year | YYYY | Yes | - | Year (e.g., 2025) |
month | 1-12 | Yes | - | Month (1-12) |
timezone | string | No | UTC | Guest's timezone (IANA format) |
Response:
{
"success": true,
"data": {
"year": 2025,
"month": 1,
"timezone": "America/New_York",
"bookingLinkTimezone": "America/Los_Angeles",
"bookingLink": {
"id": "uuid",
"name": "Guest Interview",
"duration_minutes": 60,
"timezone": "America/Los_Angeles"
},
"availableDates": [
{ "date": "2025-01-13", "slots": 1 },
{ "date": "2025-01-14", "slots": 1 },
{ "date": "2025-01-15", "slots": 1 },
{ "date": "2025-01-20", "slots": 1 }
]
}
}Note: The slots count is a placeholder (always 1). The month endpoint performs a lightweight check based on availability rules only, without querying calendars. Actual slot counts are computed when the user selects a specific date.
Timezone Handling
The availability API handles timezone conversions between three contexts:
- Guest Timezone: Where the guest is located (from
timezonequery param) - Host Timezone: The booking link's configured timezone (
booking_links.timezone) - UTC: Storage and calendar API communication
Critical Implementation Details
Creating Dates in Timezone
// src/api/routes/availability/index.ts
function createDateInTimezone(dateStr: string, timeStr: string, timezone: string): Date {
// Uses Intl.DateTimeFormat to calculate UTC offset for target timezone
// Handles DST transitions correctly
// Returns Date object representing the correct UTC instant
}Day of Week in Timezone
function getDayOfWeekInTimezone(dateStr: string, timezone: string): number {
// Important: "2024-01-15" might be Monday in New York but Tuesday in Tokyo
// Uses booking link's timezone to determine correct day of week
// Returns 0=Sunday through 6=Saturday
}Example Scenario
- Booking Link: Configured for
America/Los_Angeles(PST, UTC-8) - Availability Rule: Monday 9:00-17:00 (in host's timezone)
- Guest: In
America/New_York(EST, UTC-5)
When guest requests availability for Monday:
- Host's 9:00 PST = 12:00 EST for guest display
- Slots are generated in host's timezone, stored as UTC
- Guest receives UTC ISO strings to convert locally
Availability Rules
Availability rules define when a host is available for bookings.
Rule Structure
// src/lib/types/booking.types.ts
interface AvailabilityRule {
dayOfWeek: number; // 0=Sunday, 1=Monday, ..., 6=Saturday
startTime: string; // "09:00" (24-hour format, HH:MM)
endTime: string; // "17:00" (24-hour format, HH:MM)
}Multiple Rules Per Day
A booking link can have multiple availability windows per day:
{
"availability_rules": [
{ "dayOfWeek": 1, "startTime": "09:00", "endTime": "12:00" },
{ "dayOfWeek": 1, "startTime": "14:00", "endTime": "17:00" }
]
}This creates morning and afternoon slots on Mondays, with a lunch break.
Blocked Days
Days without rules are completely blocked. If no rules exist for Wednesday (day 3), no slots will be available on Wednesdays.
Conflict Detection
Google Calendar Integration
When checking availability, the API:
- Fetches host's calendar connections via
podcast_members(owner/admin roles) - Refreshes OAuth tokens if needed
- Queries Google Calendar FreeBusy API for each connection's
calendars_for_availability - Merges busy periods from all calendars
- Excludes busy periods from available slots
// Fetch busy times for selected calendars
const busyPeriods = await getBusyTimes(
accessToken,
connection.calendars_for_availability,
startOfDay,
endOfDay
);Existing Bookings
Confirmed and pending bookings for the same booking link are excluded:
// Query existing bookings
const { data: existingBookings } = await supabase
.from('bookings')
.select('start_time, end_time')
.eq('booking_link_id', bookingLinkId)
.in('status', ['pending', 'confirmed'])
.gte('start_time', `${date}T00:00:00Z`)
.lte('start_time', `${date}T23:59:59Z`);Buffer Time
Buffers ensure time between bookings for preparation:
- buffer_before_minutes: Minutes before the slot that must be free
- buffer_after_minutes: Minutes after the slot that must be free
Example: 60-minute slot at 10:00 with 15-minute buffers:
- Effective block: 9:45 - 11:15
- Next available slot: 11:15 or later
// Slot generation includes buffer spacing
slotStart = new Date(slotStart.getTime() + (durationMinutes + bufferAfter + bufferBefore) * 60000);Constraints
Minimum Notice
min_notice_hours prevents last-minute bookings:
const minNoticeDate = new Date(now.getTime() + (bookingLink.min_notice_hours || 24) * 60 * 60000);
// Skip slots before min notice time
if (slot.start < minNoticeDate) {
continue;
}Maximum Advance
max_advance_days limits how far in advance guests can book:
const maxAdvanceDate = new Date(
now.getTime() + (bookingLink.max_advance_days || 60) * 24 * 60 * 60000
);
// Date must be within max advance window
if (requestedDate > maxAdvanceDate) {
return { slots: [], message: '...' };
}Error Responses
| Code | Error | Cause |
|---|---|---|
400 | Validation error | Invalid UUID, date format, or timezone |
404 | Booking link not found or inactive | ID doesn't exist or is_active: false |
500 | Server configuration error | Missing Supabase credentials |
500 | No host found for this podcast | No owner/admin in podcast_members |
500 | Failed to fetch availability | General error (calendar API, database) |
TypeScript Client Usage
import { createApiClient } from '$api/client';
const client = createApiClient(fetch);
// Get slots for a date (no auth required)
const slotsRes = await client.api.availability.$get({
query: {
bookingLinkId: 'uuid',
date: '2025-01-15',
timezone: 'America/New_York'
}
});
// Get available dates in month
const monthRes = await client.api.availability.month.$get({
query: {
bookingLinkId: 'uuid',
year: '2025',
month: '1',
timezone: 'America/New_York'
}
});Performance Considerations
- Month View: Uses simplified check (availability rules only) - no calendar API calls
- Day View: Full check including calendar API calls and booking queries
- Calendar Token Refresh: Automatic, adds ~200ms latency when needed
- No Caching: Calendar busy times are fetched fresh per request
- Multiple Connections: All host calendar connections are checked (parallel token refresh)
Data Flow Diagram
Related
- Booking Links API - Event type configuration
- Bookings API - Creating bookings from available slots
- Calendars API - Calendar connection management
- Google Calendar Integration - Calendar sync implementation