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, notmessage.reasonis a closed union of ten values;messageis a fixed sentence meant for your logs. - Empty
dataplus 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
- Always Use Try-Catch: Wrap all SDK calls in try-catch blocks
- Check Error Types: Use
instanceof RecalErrorto identify SDK errors - Log Error Details: Include
statusCodeanddetailsin logs for debugging - Handle Specific Codes: Implement different logic for different status codes
- Implement Retries: Retry transient errors (429, 500) with exponential backoff
- User-Friendly Messages: Convert technical errors to user-friendly messages
- Monitor Errors: Track error rates and patterns in your monitoring system
- 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' }
}
}Related
- 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
reasoncodes