LogoRecal
JS/TS SDK

Error Handling

Handle errors and exceptions in the Recal SDK.

Understanding Error Handling

The Recal SDK provides a comprehensive error handling system through the RecalError class. All SDK methods throw typed errors that you can catch and handle appropriately in your application.


RecalError Class

The SDK uses a custom RecalError class that extends the native JavaScript Error with additional properties:

class RecalError extends Error {
    statusCode?: number   // HTTP status code (404, 400, 500, etc.)
    details?: unknown     // Additional error details from the API
}

Basic Error Handling

Wrap SDK calls in try-catch blocks to handle errors:

import { Recal, RecalError } from 'recal-sdk'

const recal = new Recal({ token: process.env.RECAL_TOKEN })

try {
    const user = await recal.users.get('user_123')
    console.log('User found:', user)
} catch (error) {
    if (error instanceof RecalError) {
        console.error('Recal API Error:', error.message)
        console.error('Status Code:', error.statusCode)
        console.error('Details:', error.details)
    } else {
        console.error('Unexpected error:', error)
    }
}

Common Error Status Codes

400 Bad Request

Invalid request parameters or missing required fields.

try {
    await recal.events.createEvent('user_id', 'google', 'primary', {
        subject: 'Meeting',
        // Missing required 'start' and 'end' fields
    })
} catch (error) {
    if (error instanceof RecalError && error.statusCode === 400) {
        console.error('Invalid request:', error.message)
        // Handle validation errors
    }
}

401 Unauthorized

Invalid or missing API token.

const recal = new Recal({ token: 'invalid_token' })

try {
    await recal.users.list()
} catch (error) {
    if (error instanceof RecalError && error.statusCode === 401) {
        console.error('Authentication failed - check your API token')
        // Prompt user to re-authenticate or check token
    }
}

404 Not Found

Resource doesn't exist (user, organization, event, etc.).

try {
    await recal.users.get('nonexistent_user')
} catch (error) {
    if (error instanceof RecalError && error.statusCode === 404) {
        console.error('User not found')
        // Handle missing resource
    }
}

429 Too Many Requests

Rate limit exceeded.

try {
    await recal.calendar.listEvents('user_id', {
        start: '2024-01-01T00:00:00Z',
        end: '2024-12-31T23:59:59Z'
    })
} catch (error) {
    if (error instanceof RecalError && error.statusCode === 429) {
        console.error('Rate limit exceeded - please retry later')
        // Implement exponential backoff
    }
}

500 Internal Server Error

Server-side error.

try {
    await recal.oauth.verifyCode('google', { code: 'auth_code', state: 'state' })
} catch (error) {
    if (error instanceof RecalError && error.statusCode === 500) {
        console.error('Server error - please try again')
        // Log error and notify monitoring system
    }
}

Error Handling Patterns

Centralized Error Handler

Create a reusable error handler for consistent error handling:

function handleRecalError(error: unknown, context: string) {
    if (error instanceof RecalError) {
        console.error(`[${context}] Recal API Error:`, {
            message: error.message,
            statusCode: error.statusCode,
            details: error.details
        })

        // Handle specific status codes
        switch (error.statusCode) {
            case 400:
                return { error: 'Invalid request', retry: false }
            case 401:
                return { error: 'Authentication failed', retry: false }
            case 404:
                return { error: 'Resource not found', retry: false }
            case 429:
                return { error: 'Rate limit exceeded', retry: true }
            case 500:
                return { error: 'Server error', retry: true }
            default:
                return { error: 'Unknown error', retry: false }
        }
    }

    console.error(`[${context}] Unexpected error:`, error)
    return { error: 'Unexpected error', retry: false }
}

// Usage
try {
    await recal.users.create('user_123')
} catch (error) {
    const result = handleRecalError(error, 'User Creation')
    if (result.retry) {
        // Implement retry logic
    }
}

Retry with Exponential Backoff

Handle transient errors with automatic retries:

async function withRetry<T>(
    fn: () => Promise<T>,
    maxRetries = 3,
    initialDelay = 1000
): Promise<T> {
    let lastError: Error

    for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {
            return await fn()
        } catch (error) {
            lastError = error as Error

            // Only retry on specific errors
            if (error instanceof RecalError) {
                const shouldRetry = [429, 500, 503].includes(error.statusCode || 0)

                if (!shouldRetry || attempt === maxRetries - 1) {
                    throw error
                }

                const delay = initialDelay * Math.pow(2, attempt)
                console.log(`Retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`)
                await new Promise(resolve => setTimeout(resolve, delay))
            } else {
                throw error
            }
        }
    }

    throw lastError!
}

// Usage
const user = await withRetry(() => recal.users.get('user_123'))

Graceful Degradation

Handle errors gracefully without breaking your application:

async function getUserCalendars(userId: string) {
    try {
        return await recal.calendar.list(userId)
    } catch (error) {
        if (error instanceof RecalError) {
            console.warn('Failed to fetch calendars:', error.message)
            return [] // Return empty array as fallback
        }
        throw error
    }
}

Partial results are not errors

The availability methods can answer incompletely — some calendars read, some not. That is a 200, so no RecalError is thrown and a try-catch will never see it. The gap arrives as data instead:

const { data, failedCalendars } = await recal.calendar.getBusyTimes('user_id', range)

if (failedCalendars.length > 0) {
    // `data` covers only the calendars we could read
    for (const calendar of failedCalendars) {
        switch (calendar.reason) {
            case 'authorizationInvalid':
            case 'insufficientScopes':
                await promptReconnect('user_id', calendar.provider)
                break
            case 'rateLimited':
            case 'providerError':
                scheduleRetry()
                break
            default:
                console.warn(`Skipped ${calendar.calendarId}: ${calendar.message}`)
        }
    }
}

The organization methods carry failedUsers in the same position, and getMultiUserSlots() carries failedCalendars on each ok entry plus an optional reason on each error entry.

Three things to know before you write the branch:

  • Both lists are always present, [] when nothing failed — no optional-chaining needed.
  • Switch on reason, not message. reason is a closed union of ten values; message is a fixed sentence meant for your logs.
  • Empty data plus a non-empty list is not "available". Fail closed in anything that books.

See Troubleshooting for what each reason means and what to show your user.


Best Practices

  1. Always Use Try-Catch: Wrap all SDK calls in try-catch blocks
  2. Check Error Types: Use instanceof RecalError to identify SDK errors
  3. Log Error Details: Include statusCode and details in logs for debugging
  4. Handle Specific Codes: Implement different logic for different status codes
  5. Implement Retries: Retry transient errors (429, 500) with exponential backoff
  6. User-Friendly Messages: Convert technical errors to user-friendly messages
  7. Monitor Errors: Track error rates and patterns in your monitoring system
  8. Validate Input: Catch validation errors early before making API calls

Type Safety

TypeScript provides type checking for error handling:

import { Recal, RecalError } from 'recal-sdk'

async function safeUserFetch(userId: string) {
    const recal = new Recal({ token: process.env.RECAL_TOKEN })

    try {
        const user = await recal.users.get(userId)
        return { success: true, data: user }
    } catch (error) {
        // Type guard ensures proper error handling
        if (error instanceof RecalError) {
            return {
                success: false,
                error: error.message,
                statusCode: error.statusCode
            }
        }
        return { success: false, error: 'Unknown error' }
    }
}

  • See Users for user-specific operations
  • See Events for event-specific operations
  • See OAuth for OAuth-specific error handling
  • See Troubleshooting for partial availability answers and the reason codes

On this page