LogoRecal
JS/TS SDK

Scheduling

Find optimal time slots for meetings and appointments.

Understanding Scheduling

The Scheduling API is your intelligent assistant for finding optimal meeting times and managing availability across users and organizations. It analyzes calendar data, applies business rules, and returns ready-to-book time slots, eliminating the back-and-forth of manual scheduling coordination.

Key Concepts

Smart Slot Generation: Automatically finds available time periods based on calendar data, working hours, and your preferences.

Multi-Calendar Awareness: Considers calendars across all connected providers (Google, Microsoft, etc.) to prevent conflicts and double-booking.

Business Rules Integration: Applies working hours, padding between meetings, and custom schedules to match real-world constraints.

Team Coordination: Finds times when multiple people or entire organizations are available simultaneously.

Timezone Intelligence: Handles timezone conversions and displays results in the appropriate timezone for each participant.


Single User Scheduling

Basic User Availability

Find available time slots for a single user with simple constraints.

// Find available time slots (minimal config)
const slots = await recal.scheduling.getSlots('user_id', {
    start: '2024-01-15T00:00:00Z',
    end: '2024-01-20T23:59:59Z',
    slotDuration: '30'  // Only required: slot duration in minutes
})

// Or with more options
const detailedSlots = await recal.scheduling.getSlots('user_id', {
    start: '2024-01-15T00:00:00Z',
    end: '2024-01-20T23:59:59Z',
    slotDuration: '30',  // Duration of each slot in minutes
    padding: '10',  // Padding between slots (minutes)
    maxOverlaps: '0',  // Only completely free slots
    earliestTimeEachDay: '09:00',  // Format: HH:mm
    latestTimeEachDay: '17:00',  // Format: HH:mm
    provider: 'google',  // optional: filter by provider
    timeZone: 'America/New_York'  // optional
})

Parameters:

  • start (required): ISO 8601 timestamp for range start
  • end (required): ISO 8601 timestamp for range end
  • slotDuration (required): Duration of each slot in minutes (as string)
  • padding (optional): Minutes between meetings (as string, default: '0')
  • maxOverlaps (optional): Allow slots with up to N overlapping events (as string, default: '0')
  • earliestTimeEachDay (optional): Start of working hours (HH:mm format)
  • latestTimeEachDay (optional): End of working hours (HH:mm format)
  • provider (optional): Filter by 'google' or 'microsoft'
  • timeZone (optional): Timezone for results

Advanced User Availability

For more complex scheduling requirements with custom work schedules per day and advanced parameters.

// Define custom schedules
const slots = await recal.scheduling.getAdvancedSlots(
    'user_id',
    {
        start: '2024-01-15T00:00:00Z',
        end: '2024-01-20T23:59:59Z',
        slotDuration: '60',
        padding: '15',
        maxOverlaps: '1'  // Allow up to 1 overlapping event
    },
    {
        schedules: [
            {
                days: ['monday', 'wednesday', 'friday'],
                start: '09:00',
                end: '17:00'
            },
            {
                days: ['tuesday', 'thursday'],
                start: '10:00',
                end: '16:00'
            }
        ]
    }
)

Query Parameters:

  • start (required): ISO 8601 timestamp for range start
  • end (required): ISO 8601 timestamp for range end
  • slotDuration (required): Duration of each slot in minutes (as string)
  • padding (optional): Minutes between meetings (as string, default: '0')
  • maxOverlaps (optional): Allow slots with up to N overlapping events (as string, default: '0')
  • provider (optional): Filter by 'google' or 'microsoft'
  • timeZone (optional): Timezone for results

Schedule Options:

  • days: Array of day names ('monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday')
  • start: Working hours start time (HH:mm format)
  • end: Working hours end time (HH:mm format)

Note: The maxOverlaps parameter is useful for scenarios where some event conflicts are acceptable (e.g., optional meetings, tentative events). When set to 0 (default), only completely free slots are returned.


Multi-User Scheduling

Find available time slots for multiple users independently. This endpoint processes each user separately and returns individual availability results.

Important: This is NOT a "find common availability" endpoint. Each user's available slots are calculated independently based on their own calendars and schedules. If you need to find times when ALL users are available simultaneously, you'll need to intersect the results yourself.

Need a built-in common availability finder or other scheduling features? If you think a native "find common slots" feature or any other scheduling functionality would be useful for your use case, we'd love to hear from you! Please reach out to hello@recal.dev and explain your specific requirements.

// Basic multi-user availability
const results = await recal.scheduling.getMultiUserSlots(
    {
        start: '2024-01-15T00:00:00Z',
        end: '2024-01-19T23:59:59Z',
        slotDuration: '60'
    },
    {
        users: [
            { id: 'user_1' },
            { id: 'user_2' },
            { id: 'user_3' }
        ]
    }
)

// With custom schedules and calendars per user
const advancedResults = await recal.scheduling.getMultiUserSlots(
    {
        start: '2024-01-15T00:00:00Z',
        end: '2024-01-19T23:59:59Z',
        slotDuration: '60',
        padding: '15',
        maxOverlaps: '1',  // Allow up to 1 overlapping event per user
        provider: 'google',  // Filter to specific provider
        timeZone: 'America/New_York'
    },
    {
        users: [
            {
                id: 'user_1',
                calendarIds: ['primary', 'work@company.com'],  // Specific calendars
                schedules: [
                    {
                        days: ['monday', 'wednesday', 'friday'],
                        start: '09:00',
                        end: '17:00'
                    }
                ]
            },
            {
                id: 'user_2',
                schedules: [
                    {
                        days: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'],
                        start: '10:00',
                        end: '16:00'
                    }
                ]
            },
            { id: 'user_3' }  // Uses default constraints
        ]
    }
)

Query Parameters:

  • start (required): ISO 8601 timestamp for range start
  • end (required): ISO 8601 timestamp for range end
  • slotDuration (required): Duration of each slot in minutes (as string)
  • padding (optional): Minutes between meetings (as string, default: '0')
  • maxOverlaps (optional): Allow slots with up to N overlapping events (as string, default: '0')
  • provider (optional): Filter by 'google' or 'microsoft'
  • timeZone (optional): Timezone for results

User Configuration:

  • id (required): User ID
  • calendarIds (optional): Specific calendar IDs to check (defaults to ['primary'])
  • schedules (optional): Array of custom schedule rules for this user

Response Format:

The endpoint returns an array with one entry per user. Each entry contains either:

Success Response:

{
    userId: string,              // The user ID you provided
    availableSlots: Array<{      // Available time slots for this user
        start: string,           // ISO 8601 timestamp
        end: string              // ISO 8601 timestamp
    }>,
    options: {                   // The scheduling parameters used
        padding: number,
        slotDuration: number,
        start: string,
        end: string,
        schedules?: Array<...>,
        maxOverlaps: number,
        calendarIds?: string[]
    }
}

Error Response:

{
    userId: string,              // The user ID you provided
    error: string                // Error message (e.g., "User not found", "User has no connected calendars")
}

Example Response:

[
    {
        userId: 'user_1',
        availableSlots: [
            { start: '2024-01-15T09:00:00Z', end: '2024-01-15T10:00:00Z' },
            { start: '2024-01-15T14:00:00Z', end: '2024-01-15T15:00:00Z' }
        ],
        options: {
            padding: 15,
            slotDuration: 60,
            start: '2024-01-15T00:00:00.000Z',
            end: '2024-01-19T23:59:59.000Z',
            maxOverlaps: 0
        }
    },
    {
        userId: 'user_2',
        availableSlots: [
            { start: '2024-01-15T10:00:00Z', end: '2024-01-15T11:00:00Z' }
        ],
        options: { /* ... */ }
    },
    {
        userId: 'user_3',
        error: 'User has no connected calendars'
    }
]

Key Behaviors:

  • Each user is processed independently and concurrently
  • If one user fails, others still succeed (error isolation)
  • Users without OAuth connections return an error response
  • Non-existent users return "User not found" error
  • Empty availableSlots array means the user is completely booked

Use Cases:

  • Displaying individual availability for multiple team members
  • Building a team scheduling UI showing each person's free slots
  • Batch processing availability checks
  • Comparing schedules across team members

Organization-Wide Scheduling

For team-wide availability, you can also use organization-level scheduling methods:

// Find organization-wide available time slots
const orgSlots = await recal.organizations.getScheduling('org-slug', {
    start: '2024-01-15T00:00:00Z',
    end: '2024-01-20T23:59:59Z',
    slotDuration: '60',
    padding: '15',
    maxOverlaps: '0',  // Only completely free slots
    earliestTimeEachDay: '09:00',
    latestTimeEachDay: '17:00',
    provider: ['google', 'microsoft'],
    timeZone: 'America/New_York'
})

Parameters:

  • start (required): ISO 8601 timestamp for range start
  • end (required): ISO 8601 timestamp for range end
  • slotDuration (required): Duration of each slot in minutes (as string)
  • padding (optional): Minutes between meetings (as string, default: '0')
  • maxOverlaps (optional): Allow slots with up to N overlapping events (as string, default: '0')
  • earliestTimeEachDay (optional): Start of working hours (HH:mm format)
  • latestTimeEachDay (optional): End of working hours (HH:mm format)
  • provider (optional): Filter by provider(s)
  • timeZone (optional): Timezone for results

See Organizations for more details on team scheduling.


Response Format

Single User Methods

Both getSlots() and getAdvancedSlots() return an object with available slots and the options used:

{
    availableSlots: [
        {
            start: '2024-01-15T10:00:00Z',
            end: '2024-01-15T10:30:00Z'
        },
        {
            start: '2024-01-15T14:00:00Z',
            end: '2024-01-15T14:30:00Z'
        },
        {
            start: '2024-01-16T09:00:00Z',
            end: '2024-01-16T09:30:00Z'
        }
    ],
    options: {
        padding: 15,
        slotDuration: 30,
        start: '2024-01-15T00:00:00.000Z',
        end: '2024-01-20T23:59:59.000Z',
        maxOverlaps: 0
    }
}

Multi-User Methods

getMultiUserSlots() returns an array with one entry per user - see the detailed response format in the Multi-User Scheduling section above.


Use Cases

Simple Booking System

async function findNextAvailableSlot(userId: string) {
    const now = new Date()
    const oneWeekLater = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000)

    const result = await recal.scheduling.getSlots(userId, {
        start: now.toISOString(),
        end: oneWeekLater.toISOString(),
        slotDuration: '30',
        padding: '10',
        earliestTimeEachDay: '09:00',
        latestTimeEachDay: '17:00'
    })

    return result.availableSlots[0]  // Return first available slot
}

Team Availability Dashboard

async function getTeamAvailability(userIds: string[]) {
    const startDate = new Date()
    const endDate = new Date(startDate.getTime() + 14 * 24 * 60 * 60 * 1000)

    const results = await recal.scheduling.getMultiUserSlots(
        {
            start: startDate.toISOString(),
            end: endDate.toISOString(),
            slotDuration: '60',
            padding: '15'
        },
        {
            users: userIds.map(id => ({ id }))
        }
    )

    // Process results for each user
    return results.map(result => {
        if ('error' in result) {
            return {
                userId: result.userId,
                error: result.error,
                slots: []
            }
        }
        return {
            userId: result.userId,
            slots: result.availableSlots.slice(0, 5)  // First 5 slots per user
        }
    })
}

// Find common availability across all users
async function findCommonAvailability(userIds: string[]) {
    const startDate = new Date()
    const endDate = new Date(startDate.getTime() + 7 * 24 * 60 * 60 * 1000)

    const results = await recal.scheduling.getMultiUserSlots(
        {
            start: startDate.toISOString(),
            end: endDate.toISOString(),
            slotDuration: '60',
            padding: '15'
        },
        {
            users: userIds.map(id => ({ id }))
        }
    )

    // Filter out errors and get only successful results
    const successResults = results.filter(r => 'availableSlots' in r)

    if (successResults.length === 0) {
        return []
    }

    // Find slots that appear in ALL users' availability
    const firstUserSlots = successResults[0].availableSlots
    return firstUserSlots.filter(slot =>
        successResults.every(result =>
            result.availableSlots.some(s =>
                s.start === slot.start && s.end === slot.end
            )
        )
    )
}

Custom Work Schedule

async function findSlotsWithFlexibleHours(userId: string) {
    const result = await recal.scheduling.getAdvancedSlots(
        userId,
        {
            start: '2024-01-15T00:00:00Z',
            end: '2024-01-20T23:59:59Z',
            slotDuration: '45',
            padding: '15'
        },
        {
            schedules: [
                {
                    days: ['monday', 'wednesday', 'friday'],
                    start: '08:00',
                    end: '12:00'
                },
                {
                    days: ['tuesday', 'thursday'],
                    start: '13:00',
                    end: '18:00'
                }
            ]
        }
    )

    return result.availableSlots
}

Best Practices

  1. Appropriate Date Ranges: Query 1-2 weeks at a time for optimal performance
  2. Reasonable Slot Durations: Use 15, 30, or 60-minute slots for best results
  3. Include Padding: Add 5-15 minutes between meetings for buffer time
  4. Set Working Hours: Use earliestTimeEachDay and latestTimeEachDay to respect work-life balance
  5. Timezone Awareness: Always specify timezone when displaying slots to users
  6. Cache Results: Slot availability is relatively stable - cache for 5-15 minutes
  7. Limit User Count: For multi-user scheduling, optimal performance with 2-10 users
  8. Use Custom Schedules: For complex availability patterns, use getAdvancedSlots() or per-user schedules

On this page