Skip to main content
The Recall AI SDK provides calendar integration to automatically join meetings from Google Calendar and Microsoft Outlook.

Overview

Calendar integration allows you to:
  • Connect user calendar accounts via OAuth
  • Automatically detect upcoming meetings
  • Deploy bots to scheduled meetings
  • Manage multiple calendar connections
  • Update or remove calendar integrations

Supported platforms

Google Calendar

Connect Google Workspace and Gmail accounts to access calendar events.

Microsoft Outlook

Connect Microsoft 365 and Outlook accounts for calendar access.

Creating a calendar connection

1

Obtain OAuth credentials

Get OAuth credentials from your calendar provider:
2

Get user's refresh token

Complete the OAuth flow to obtain the user’s refresh token. The refresh token allows Recall to access the calendar on the user’s behalf.
3

Create calendar connection

Use the SDK to register the calendar connection:
const calendar = await recall.calendar.create({
  platform: 'google_calendar',
  oauth_client_id: 'your-client-id',
  oauth_client_secret: 'your-client-secret',
  oauth_refresh_token: 'user-refresh-token',
  oauth_email: '[email protected]'
});

console.log(`Calendar connected: ${calendar.oauth_email}`);

Calendar connection parameters

platform
CalendarPlatform
required
The calendar platform to connect:
  • google_calendar - Google Calendar
  • microsoft_outlook - Microsoft Outlook/365
oauth_client_id
string
required
OAuth client ID from the calendar provider.
oauth_client_secret
string
required
OAuth client secret from the calendar provider.
oauth_refresh_token
string
required
User’s OAuth refresh token obtained through the OAuth flow.
oauth_email
string
The email address associated with the calendar account. Recommended for tracking and debugging.

Retrieving calendar connections

Get details about a specific calendar connection:
const calendar = await recall.calendar.retrieve({ id: 'calendar-id' });

console.log(`Email: ${calendar.oauth_email}`);
console.log(`Platform: ${calendar.platform}`);
console.log(`Status: ${calendar.status}`);

Listing calendar connections

Query and filter calendar connections:
const calendars = await recall.calendar.list({});

calendars.results.forEach(cal => {
  console.log(`${cal.oauth_email} (${cal.platform}): ${cal.status}`);
});

Calendar status values

Calendar connections have the following statuses:
StatusDescription
connectingInitial connection is being established
connectedCalendar is successfully connected and active
disconnectedCalendar connection has been lost or revoked
If a calendar shows as disconnected, the user may need to re-authorize access through your OAuth flow.

Updating calendar connections

Update OAuth credentials or settings:
const updated = await recall.calendar.update({
  id: 'calendar-id',
  oauth_refresh_token: 'new-refresh-token',
  oauth_client_id: 'new-client-id',
  oauth_client_secret: 'new-client-secret'
});

console.log('Calendar updated successfully');
Updating credentials may temporarily disconnect the calendar. Ensure the new credentials are valid before updating.

Deleting calendar connections

Remove a calendar connection:
await recall.calendar.delete({ id: 'calendar-id' });

console.log('Calendar connection removed');
Deleting a calendar connection will stop automatic bot deployment for meetings in that calendar.

Automatic meeting detection

Once connected, Recall automatically:
  1. Monitors the calendar for upcoming meetings
  2. Detects meeting URLs in calendar events
  3. Deploys bots to join scheduled meetings
  4. Handles meeting updates and cancellations
Recall looks for meeting URLs in calendar event descriptions, locations, and conferencing details. Supported platforms include Zoom, Google Meet, Microsoft Teams, and more.

Handling OAuth flow

Implement the OAuth flow to obtain user consent:
import { google } from 'googleapis';

const oauth2Client = new google.auth.OAuth2(
  process.env.GOOGLE_CLIENT_ID,
  process.env.GOOGLE_CLIENT_SECRET,
  'https://yourapp.com/oauth/callback'
);

// Generate authorization URL
const authUrl = oauth2Client.generateAuthUrl({
  access_type: 'offline',
  scope: ['https://www.googleapis.com/auth/calendar.readonly'],
  prompt: 'consent'
});

// Redirect user to authUrl
console.log('Authorize here:', authUrl);

// After user authorizes, exchange code for tokens
async function handleCallback(code) {
  const { tokens } = await oauth2Client.getToken(code);
  
  // Create calendar connection in Recall
  const calendar = await recall.calendar.create({
    platform: 'google_calendar',
    oauth_client_id: process.env.GOOGLE_CLIENT_ID,
    oauth_client_secret: process.env.GOOGLE_CLIENT_SECRET,
    oauth_refresh_token: tokens.refresh_token,
    oauth_email: '[email protected]' // Get from user profile
  });
  
  return calendar;
}

Error handling

Handle common calendar integration errors:
async function connectCalendar(userEmail, refreshToken) {
  try {
    const calendar = await recall.calendar.create({
      platform: 'google_calendar',
      oauth_client_id: process.env.GOOGLE_CLIENT_ID,
      oauth_client_secret: process.env.GOOGLE_CLIENT_SECRET,
      oauth_refresh_token: refreshToken,
      oauth_email: userEmail
    });
    
    return { success: true, calendar };
    
  } catch (error) {
    if (error.response?.status === 401) {
      // Invalid or expired OAuth credentials
      return { 
        success: false, 
        error: 'Please re-authorize calendar access' 
      };
    } else if (error.response?.status === 400) {
      // Invalid parameters
      return { 
        success: false, 
        error: 'Invalid calendar configuration' 
      };
    } else {
      // Other errors
      return { 
        success: false, 
        error: 'Failed to connect calendar' 
      };
    }
  }
}

Monitoring calendar health

Regularly check calendar connection status:
async function checkCalendarHealth() {
  const calendars = await recall.calendar.list({});
  
  const health = {
    total: calendars.results.length,
    connected: 0,
    disconnected: 0,
    issues: []
  };
  
  calendars.results.forEach(cal => {
    if (cal.status === 'connected') {
      health.connected++;
    } else if (cal.status === 'disconnected') {
      health.disconnected++;
      health.issues.push({
        email: cal.oauth_email,
        platform: cal.platform,
        status: cal.status
      });
    }
  });
  
  return health;
}

// Run health check periodically
setInterval(async () => {
  const health = await checkCalendarHealth();
  
  if (health.disconnected > 0) {
    console.warn(`${health.disconnected} calendars disconnected`);
    health.issues.forEach(issue => {
      console.warn(`  - ${issue.email} (${issue.platform})`);
    });
    
    // Notify users to reconnect
    await notifyUsersToReconnect(health.issues);
  }
}, 60 * 60 * 1000); // Every hour

Best practices

Request minimal scopes

Only request calendar read permissions. Avoid requesting unnecessary scopes.

Handle token expiry

Monitor for disconnected status and prompt users to re-authorize.

Store securely

Never expose OAuth credentials. Store them securely in your backend.

Monitor regularly

Periodically check calendar connection health and notify users of issues.

Complete example

import { Recall } from '@recall-ai/sdk';
import express from 'express';
import { google } from 'googleapis';

const app = express();
const recall = new Recall({
  apiKey: process.env.RECALL_API_KEY,
  region: 'us-west-2'
});

const oauth2Client = new google.auth.OAuth2(
  process.env.GOOGLE_CLIENT_ID,
  process.env.GOOGLE_CLIENT_SECRET,
  'http://localhost:3000/oauth/callback'
);

// Initiate calendar connection
app.get('/connect-calendar', (req, res) => {
  const authUrl = oauth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: ['https://www.googleapis.com/auth/calendar.readonly'],
    prompt: 'consent'
  });
  
  res.redirect(authUrl);
});

// OAuth callback
app.get('/oauth/callback', async (req, res) => {
  try {
    const { code } = req.query;
    
    // Exchange authorization code for tokens
    const { tokens } = await oauth2Client.getToken(code);
    
    // Get user's email
    oauth2Client.setCredentials(tokens);
    const oauth2 = google.oauth2({ version: 'v2', auth: oauth2Client });
    const { data } = await oauth2.userinfo.get();
    
    // Create calendar connection in Recall
    const calendar = await recall.calendar.create({
      platform: 'google_calendar',
      oauth_client_id: process.env.GOOGLE_CLIENT_ID,
      oauth_client_secret: process.env.GOOGLE_CLIENT_SECRET,
      oauth_refresh_token: tokens.refresh_token,
      oauth_email: data.email
    });
    
    console.log(`Calendar connected for ${data.email}`);
    res.send(`✓ Calendar connected successfully for ${data.email}`);
    
  } catch (error) {
    console.error('Error connecting calendar:', error);
    res.status(500).send('Failed to connect calendar');
  }
});

// List user's connected calendars
app.get('/calendars', async (req, res) => {
  try {
    const calendars = await recall.calendar.list({});
    res.json(calendars.results);
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch calendars' });
  }
});

// Disconnect calendar
app.delete('/calendars/:id', async (req, res) => {
  try {
    await recall.calendar.delete({ id: req.params.id });
    res.json({ message: 'Calendar disconnected' });
  } catch (error) {
    res.status(500).json({ error: 'Failed to disconnect calendar' });
  }
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
  console.log('Visit http://localhost:3000/connect-calendar to start');
});

Build docs developers (and LLMs) love