Recal SDK for JavaScript/TypeScript
A powerful, type-safe SDK for interacting with the Recal calendar API. Build sophisticated calendar integrations with support for Google and Microsoft calendar providers.

Features
- Multi-Provider Support: Seamlessly work with Google Calendar and Microsoft Outlook
- Type Safety: Full TypeScript support with runtime validation
- Rich Calendar Operations: Events, busy queries, scheduling, and more
- Organization Management: Handle organizations and users calendars
- OAuth Integration: Built-in OAuth flow support for calendar connections
- Error Handling: Comprehensive error types for robust applications
- Modern Architecture: Clean, testable service-based design
Installation
npm install recal-sdk
yarn add recal-sdk
bun add recal-sdk
Quick Start
Basic Setup
import { RecalClient } from 'recal-sdk'
const recal = new RecalClient()
const recal = new RecalClient({
token: "recal_xyz"
})
Security Note: This SDK is designed for server-side use. Never expose your API token in client-side code.
Core Concepts
Services
The SDK is organized into logical service modules:
calendar - Event management and busy queries
scheduling - Availability and booking management
users - User profile and settings
organizations - Team and organization management
oauth - Calendar provider authentication
Time Zones
All date/time operations support timezone specification via the timeZone parameter.
API Reference
TypeScript note
- Use the exported
Provider enum for provider arguments.
- Date/time fields in responses are parsed into
Date objects at runtime.
import { Provider } from 'recal-sdk'
Calendar Service
Get Busy Information
const busy = await recal.calendar.getBusy(
'user_id',
new Date('2024-01-01'),
new Date('2024-01-07')
)
const busyFiltered = await recal.calendar.getBusy(
'user_id',
new Date('2024-01-01'),
new Date('2024-01-07'),
{
provider: 'google',
timeZone: 'America/New_York',
}
)
List Events
const events = await recal.calendar.getEvents(
'user_id',
new Date('2024-01-01'),
new Date('2024-01-31')
)
const eventsFiltered = await recal.calendar.getEvents(
'user_id',
new Date('2024-01-01'),
new Date('2024-01-31'),
{
provider: 'google',
timeZone: 'Europe/London'
}
)
Create Event
const event = await recal.calendar.createEvent({
userId: 'user_id',
provider: 'google',
calendarId: 'calendar_id',
event: {
subject: 'Team Meeting',
description: 'Weekly sync',
start: new Date('2024-01-15T10:00:00Z'),
end: new Date('2024-01-15T11:00:00Z'),
attendees: [
{ email: 'colleague@company.com' }
]
}
})
const eventWithTZ = await recal.calendar.createEvent({
userId: 'user_id',
provider: 'google',
calendarId: 'calendar_id',
event: {
subject: 'Team Meeting',
description: 'Weekly sync',
start: new Date('2024-01-15T10:00:00Z'),
end: new Date('2024-01-15T11:00:00Z'),
attendees: [
{ email: 'colleague@company.com' }
]
},
options: { timeZone: 'Europe/Berlin' }
})
Get Event
const event = await recal.calendar.getEvent({
userId: 'user_id',
provider: 'google',
calendarId: 'calendar_id',
eventId: 'event_id',
options: { timeZone: 'Europe/Berlin' }
})
Update Event
const updated = await recal.calendar.updateEvent({
userId: 'user_id',
provider: 'google',
calendarId: 'calendar_id',
eventId: 'event_id',
event: {
subject: 'Updated Meeting Title',
start: new Date('2024-01-15T14:00:00Z'),
end: new Date('2024-01-15T15:00:00Z')
}
})
const updated = await recal.calendar.updateEvent({
userId: 'user_id',
provider: 'google',
calendarId: 'calendar_id',
eventId: 'event_id',
event: {
subject: 'Updated Meeting title',
description: 'Updated description',
start: new Date('2024-01-15T11:00:00Z'),
end: new Date('2024-01-15T12:00:00Z'),
attendees: [
{ email: 'colleague@company.com' }
]
},
options: { timeZone: 'Europe/Berlin' }
})
Delete Event
await recal.calendar.deleteEvent({
userId: 'user_id',
provider: 'google',
calendarId: 'calendar_id',
eventId: 'event_id'
})
Cross-Calendar Operations (Meta Events)
Meta events allow you to work with events across multiple calendar providers:
const metaEvent = await recal.calendar.createEventByMetaId(
'user_id',
{
subject: 'Cross-platform meeting',
start: new Date('2024-01-20T15:00:00Z'),
end: new Date('2024-01-20T16:00:00Z')
}
)
const metaEventSpecific = await recal.calendar.createEventByMetaId(
'user_id',
{
subject: 'Cross-platform meeting',
start: new Date('2024-01-20T15:00:00Z'),
end: new Date('2024-01-20T16:00:00Z')
},
{
provider: ['google', 'microsoft'],
timeZone: 'Europe/Berlin'
}
)
const metaEventGet = await recal.calendar.getEventByMetaId(
'user_id',
metaEvent.metaId
)
await recal.calendar.updateEventByMetaId(
'user_id',
metaEvent.metaId,
{ subject: 'Updated title' }
)
await recal.calendar.deleteEventByMetaId(
'user_id',
metaEvent.metaId
)
Scheduling Service
Get User Availability (Basic)
const availability = await recal.scheduling.userSchedulingBasic(
'user_id',
new Date('2024-01-15'),
new Date('2024-01-20'),
{
slotDuration: 30
}
)
const availabilityDetailed = await recal.scheduling.userSchedulingBasic(
'user_id',
new Date('2024-01-15'),
new Date('2024-01-20'),
{
slotDuration: 30,
padding: 0,
earliestTimeEachDay: '09:00',
latestTimeEachDay: '17:00',
provider: 'google',
timeZone: 'America/New_York'
}
)
Get User Availability (Advanced)
const schedules = [
{
days: ['monday'],
start: '09:00',
end: '17:00'
},
]
const availability = await recal.scheduling.userSchedulingAdvanced(
'user_id',
schedules,
new Date('2024-01-15'),
new Date('2024-01-20'),
{ slotDuration: 30 }
)
const availabilityDetailed = await recal.scheduling.userSchedulingAdvanced(
'user_id',
schedules,
new Date('2024-01-15'),
new Date('2024-01-20'),
{
slotDuration: 30,
padding: 15,
provider: 'google',
timeZone: 'America/New_York'
}
)
Get Organization-Wide Availability
const orgAvailability = await recal.scheduling.getOrgWideAvailability(
'org-slug',
new Date('2024-01-15'),
new Date('2024-01-20'),
{ slotDuration: 60 }
)
const orgAvailabilityConstrained = await recal.scheduling.getOrgWideAvailability(
'org-slug',
new Date('2024-01-15'),
new Date('2024-01-20'),
{
slotDuration: 60,
padding: 0,
earliestTimeEachDay: '09:00',
latestTimeEachDay: '17:00',
provider: ['google', 'microsoft'],
timeZone: 'America/New_York'
}
)
Users Service
Get User
const user = await recal.users.get('user_id', {})
const userWithDetails = await recal.users.get('user_id', {
includeOrgs: true,
includeOAuth: true
})
console.log(user.id)
List All Users
const users = await recal.users.listAll()
Create User
const user = await recal.users.create('user_id')
const userWithOrgs = await recal.users.create(
'user_id',
['org-slug-1', 'org-slug-2']
)
Update User
const updatedUser = await recal.users.update('old_user_id', {
id: 'new_user_id'
})
Delete User
const deletedUser = await recal.users.delete('user_id')
Organizations Service
Get Organization
const org = await recal.organizations.get('acme-corp')
List All Organizations
const orgs = await recal.organizations.listAll()
const userOrgs = await recal.organizations.listAllFromUser('user_id')
Create Organization
const org = await recal.organizations.create(
'acme-corp',
'Acme Corporation'
)
Update Organization
const updated = await recal.organizations.update('acme-corp', {
slug: 'new-slug',
name: 'New Name'
})
Manage Members
const members = await recal.organizations.getMembers('acme-corp')
await recal.organizations.addMembers(
'acme-corp',
['user_id_1', 'user_id_2']
)
await recal.organizations.removeMembers(
'acme-corp',
['user_id_1', 'user_id_2']
)
Organization-Wide Busy
const teamBusy = await recal.calendar.getOrgWideBusy(
'acme-corp',
new Date('2024-01-15'),
new Date('2024-01-20'),
true
)
const teamBusyFiltered = await recal.calendar.getOrgWideBusy(
'acme-corp',
new Date('2024-01-15'),
new Date('2024-01-20'),
true,
{
provider: 'google',
timeZone: 'America/New_York'
}
)
OAuth Service
Get OAuth Link
const link = await recal.oauth.getLink(
'user_id',
'google'
)
const linkWithOptions = await recal.oauth.getLink(
'user_id',
'google',
{
scope: 'edit',
accessType: 'offline',
redirectUrl: 'https://app.example.com/callback'
}
)
console.log(link.url)
Get Multiple OAuth Links
const links = await recal.oauth.getBulkLinks('user_id')
const linksFiltered = await recal.oauth.getBulkLinks(
'user_id',
{
provider: ['google', 'microsoft'],
scope: 'edit',
accessType: 'offline'
}
)
Manage OAuth Connections
const connections = await recal.oauth.getAllConnections(
'user_id',
true
)
const googleConnection = await recal.oauth.getConnection(
'user_id',
'google',
false
)
const connection = await recal.oauth.setConnection(
'user_id',
'google',
{
accessToken: 'access_token',
refreshToken: 'refresh_token',
scope: ['calendar.events', 'calendar.readonly'],
expiresAt: new Date('2024-12-31'),
email: 'user@example.com'
}
)
await recal.oauth.disconnect('user_id', 'google')
Verify OAuth Callback
const result = await recal.oauth.verify(
'google',
'auth_code_from_callback',
'edit',
'state_parameter',
'https://app.example.com/callback'
)
Advanced Usage
Error Handling
The SDK provides specific error types for different scenarios:
import {
UserNotFoundError,
EventNotFoundError,
OAuthConnectionNotFoundError,
OrganizationNotFoundError
} from 'recal-sdk'
try {
const event = await recal.calendar.getEvent({
userId: 'user_id',
provider: 'google',
calendarId: 'calendar_id',
eventId: 'event_id'
})
} catch (error) {
if (error instanceof UserNotFoundError) {
console.log('User does not exist:', error.userId)
} else if (error instanceof EventNotFoundError) {
console.log('Event not found:', error.eventId)
} else if (error instanceof OAuthConnectionNotFoundError) {
console.log('Calendar not connected:', error.provider)
}
}
Batch Operations
const userIds = ['user1', 'user2', 'user3']
const allEvents = await Promise.all(
userIds.map(userId =>
recal.calendar.getEvents(
userId,
new Date('2024-01-01'),
new Date('2024-01-31')
)
)
)
Working with Multiple Providers
const startDate = new Date('2024-01-01')
const endDate = new Date('2024-01-31')
const allBusy = await recal.calendar.getBusy(
'user_id',
startDate,
endDate
)
const providers: Provider[] = ['google', 'microsoft']
const busyTimes = await Promise.all(
providers.map(provider =>
recal.calendar.getBusy(
'user_id',
startDate,
endDate,
{ provider }
)
)
)
const allBusyPeriods = busyTimes.flat()
Custom Request Configuration
const recal = new RecalClient({
token: 'recal_token',
url: 'https://api.recal.dev'
})
Examples
Building a Booking System
const availability = await recal.scheduling.userSchedulingBasic(
'consultant_id',
new Date(),
new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
{
slotDuration: 60,
padding: 15,
earliestTimeEachDay: '09:00',
latestTimeEachDay: '17:00',
timeZone: 'America/New_York'
}
)
const availableSlots = availability.availableSlots
const selectedSlot = availableSlots[0]
const clientName = 'John Doe'
const clientEmail = 'john@example.com'
const booking = await recal.calendar.createEvent({
userId: 'consultant_id',
provider: 'google',
calendarId: 'primary',
event: {
subject: 'Consultation with ' + clientName,
description: 'Initial consultation',
start: selectedSlot.start,
end: selectedSlot.end,
attendees: [{ email: clientEmail }]
}
})
console.log('Booking confirmed:', booking.id)
Syncing Calendars
async function syncCalendars(userId: string) {
const allEvents = await recal.calendar.getEvents(
userId,
new Date(),
new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
)
const googleEvents = await recal.calendar.getEvents(
userId,
new Date(),
new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
{ provider: 'google' }
)
for (const event of googleEvents) {
if (!event.metaId) {
await recal.calendar.createEvent({
userId,
provider: 'microsoft',
calendarId: 'primary',
event: {
subject: event.subject,
description: event.description,
start: event.start,
end: event.end,
attendees: event.attendees
}
})
}
}
}
Team Scheduling
async function findTeamSlot(
orgSlug: string,
duration: number,
startDate: Date,
endDate: Date
) {
const busyTimes = await recal.calendar.getOrgWideBusy(
orgSlug,
startDate,
endDate,
true
)
const availability = await recal.scheduling.getOrgWideAvailability(
orgSlug,
startDate,
endDate,
{
slotDuration: duration,
padding: 0,
earliestTimeEachDay: '09:00',
latestTimeEachDay: '17:00'
}
)
return availability.availableSlots
}
SDK Development
Prerequisites
- Node.js 18+ or Bun 1.0+, or any other JavaScript runtime
- TypeScript 5.0+
- Biome 2.1.2 (for contributing)
Setup
git clone https://github.com/recal-dev/recal-sdk-js.git
cd recal-sdk-js
bun install
bun test
bun run build
Project Structure
src/
├── index.ts # Main client and exports
├── services/ # Service implementations
│ ├── calendar.service.ts
│ ├── scheduling.service.ts
│ ├── users.service.ts
│ ├── organizations.service.ts
│ └── oauth.service.ts
├── entities/ # Domain models
│ ├── user.ts
│ └── organization.ts
├── types/ # TypeScript type definitions
│ ├── calendar.types.ts
│ ├── scheduling.types.ts
│ ├── internal.types.ts
│ └── oauth.types.ts
├── typebox/ # Runtime validation schemas (auto-generated)
│ ├── calendar.tb.ts
│ ├── scheduling.tb.ts
│ ├── oauth.tb.ts
│ ├── organization.tb.ts
│ ├── user.tb.ts
│ ├── timeString.tb.ts
│ ├── organization.stripped.tb.ts
│ └── user.stripped.tb.ts
├── utils/ # Helper utilities
│ ├── fetch.helper.ts
│ ├── fetchErrorHandler.ts
│ ├── includes.helper.ts
│ ├── functionize.ts
│ └── omit.ts
└── errors.ts # Custom error classes
Code Style
This project uses Biome for formatting and linting:
bun run format:fix
bun run lint:fix
bun run check:fix
Testing
bun test
bun test tests/integrations/users.test.ts
bun test --coverage
Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development Workflow
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature)
- Make your changes
- Run tests and linting (
bun test && bun run check:fix)
- Commit your changes (
git commit -m 'Add amazing feature')
- Push to your branch (
git push origin feature/amazing-feature)
- Open a Pull Request
Reporting Issues
Found a bug or have a feature request? Please open an issue with:
- Clear description
- Steps to reproduce (for bugs)
- Expected vs actual behavior
- SDK version and environment details
Support
License
This SDK is licensed under the MIT License. See the LICENSE file for details.
Changelog
See CHANGELOG.md for a list of changes in each version.
Built with ❤️ by the Recal team