Skip to main content
The Recall AI SDK provides methods to control bot recording operations during and after meetings.

Recording lifecycle

Recordings go through several phases:
1

Bot joins meeting

The bot enters the meeting and waits for recording permission.
2

Recording starts

Once permission is granted (or based on start_recording_on setting), recording begins automatically.
3

Recording active

The bot captures video, audio, and transcripts in real-time.
4

Recording stops

Recording ends when you call stopRecording(), when the meeting ends, or when the bot leaves.
5

Media available

Recording files are processed and made available for download.

Automatic recording control

Configure recording start behavior

Control when recording begins using recording_mode_options:
const bot = await recall.bot.create({
  meeting_url: 'https://zoom.us/j/123456789',
  recording_mode_options: {
    start_recording_on: 'bot_join'
  }
});
The bot starts recording immediately after joining the meeting.

Stopping recordings

Stop a bot’s recording while keeping it in the meeting:
await recall.bot.stopRecording({ id: 'bot-id' });
After stopping the recording, the bot remains in the meeting with status in_call_not_recording. The recorded media is still processed and available.

When to stop recording

Confidential discussion

Stop recording when sensitive topics arise that shouldn’t be captured.

Meeting breaks

Pause recording during breaks to save storage and processing costs.

Partial recording

Record only specific segments of longer meetings.

Resource management

Stop early if you’ve captured what you need to optimize resource usage.

Example: Stop after time limit

const bot = await recall.bot.create({
  meeting_url: 'https://meet.google.com/abc-defg-hij',
  bot_name: 'Time-Limited Recorder'
});

// Stop recording after 30 minutes
setTimeout(async () => {
  const botInfo = await recall.bot.retrieve({ id: bot.id });
  
  if (botInfo.status === 'in_call_recording') {
    await recall.bot.stopRecording({ id: bot.id });
    console.log('Recording stopped after 30 minutes');
  }
}, 30 * 60 * 1000);

Leaving the call

Make the bot leave the meeting entirely:
await recall.bot.leaveCall({ id: 'bot-id' });
Leaving the call stops the recording and removes the bot from the meeting. The bot cannot rejoin the same meeting after leaving.

Stop recording vs. leave call

ActionRecordingBot in meetingRe-recording possible
stopRecording()StopsRemainsYes (via API)
leaveCall()StopsLeavesNo

Example: Conditional leave

async function leaveIfNoParticipants(botId) {
  const botInfo = await recall.bot.retrieve({ id: botId });
  
  // Check participant count from bot intelligence
  if (botInfo.participant_count <= 1) {
    await recall.bot.leaveCall({ id: botId });
    console.log('Bot left - no other participants');
  }
}

// Check every 5 minutes
setInterval(() => leaveIfNoParticipants('bot-id'), 5 * 60 * 1000);

Deleting media

Delete recorded media to free up storage:
await recall.bot.deleteBotMedia({ id: 'bot-id' });
Deleting media is permanent and irreversible. Make sure you’ve downloaded or processed any needed content first.

What gets deleted

When you delete bot media, the following are removed:
  • Video recordings
  • Audio files
  • Raw transcripts
  • Chat messages (if captured)
  • Screen share recordings
Bot metadata, intelligence summaries, and logs are not deleted. You can still retrieve bot information and status history.

When to delete media

async function processAndCleanup(botId) {
  // Wait for recording to complete
  let status = await waitForCompletion(botId);
  
  if (status === 'done') {
    // Get transcript and intelligence
    const transcript = await recall.bot.getTranscript({ id: botId });
    const intel = await recall.bot.getBotIntellienge({ id: botId });
    
    // Store in your database
    await saveToDatabase({ transcript, intel });
    
    // Delete media from Recall
    await recall.bot.deleteBotMedia({ id: botId });
    console.log('Media deleted after processing');
  }
}

Recording modes

Configure how the bot records meetings:

Speaker view

Focus on the active speaker:
const bot = await recall.bot.create({
  meeting_url: 'https://zoom.us/j/123456789',
  recording_mode: 'speaker_view',
  recording_mode_options: {
    participant_video_when_screenshare: 'show' // or 'hide'
  }
});
Capture all participants:
const bot = await recall.bot.create({
  meeting_url: 'https://teams.microsoft.com/l/meetup-join/...',
  recording_mode: 'gallery_view'
});

Audio only

Record only audio without video:
const bot = await recall.bot.create({
  meeting_url: 'https://meet.google.com/abc-defg-hij',
  recording_mode: 'audio_only'
});
Audio-only mode uses less bandwidth and storage, making it ideal for podcast recordings or when video isn’t needed.

Screen share handling

Control how screen shares are recorded:
const bot = await recall.bot.create({
  meeting_url: 'https://zoom.us/j/123456789',
  recording_mode: 'speaker_view',
  recording_mode_options: {
    participant_video_when_screenshare: 'hide'
  }
});
When someone shares their screen, participant videos are hidden and only the screen share is recorded.Best for: Presentations, demos, screen-focused meetings

Monitoring recording status

Track recording progress in real-time:
async function monitorRecording(botId) {
  const checkStatus = async () => {
    const bot = await recall.bot.retrieve({ id: botId });
    
    console.log(`Status: ${bot.status}`);
    
    switch (bot.status) {
      case 'in_call_recording':
        console.log('✓ Recording in progress');
        break;
        
      case 'recording_permission_denied':
        console.error('✗ Recording permission denied');
        await recall.bot.leaveCall({ id: botId });
        break;
        
      case 'in_call_not_recording':
        console.log('⊗ In call but not recording');
        break;
        
      case 'recording_done':
        console.log('✓ Recording complete');
        return true; // Stop monitoring
        
      case 'fatal':
        console.error('✗ Fatal error occurred');
        return true; // Stop monitoring
    }
    
    return false; // Continue monitoring
  };
  
  // Poll every 10 seconds
  const interval = setInterval(async () => {
    const done = await checkStatus();
    if (done) clearInterval(interval);
  }, 10000);
}

monitorRecording('bot-id');

Best practices

Check permissions

Always monitor for recording_permission_denied status and handle appropriately.

Implement retention

Delete media after your retention period to minimize storage costs.

Backup before delete

Download or process media before calling deleteBotMedia().

Handle errors gracefully

Clean up media from failed bots to avoid unnecessary storage charges.

Complete workflow example

import { Recall } from '@recall-ai/sdk';
import fs from 'fs';

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

async function recordAndCleanup(meetingUrl) {
  let botId;
  
  try {
    // Create bot with specific recording settings
    const bot = await recall.bot.create({
      meeting_url: meetingUrl,
      bot_name: 'Auto-Cleanup Recorder',
      recording_mode: 'speaker_view',
      recording_mode_options: {
        start_recording_on: 'bot_join',
        participant_video_when_screenshare: 'hide'
      }
    });
    
    botId = bot.id;
    console.log(`Bot created: ${botId}`);
    
    // Monitor until recording is done
    let status = 'ready';
    while (!['done', 'fatal', 'recording_done'].includes(status)) {
      await new Promise(resolve => setTimeout(resolve, 10000));
      
      const botInfo = await recall.bot.retrieve({ id: botId });
      status = botInfo.status;
      console.log(`Current status: ${status}`);
      
      // Stop recording after 60 minutes
      if (status === 'in_call_recording') {
        // Implement your logic here
      }
    }
    
    // Get transcript and intelligence
    if (status === 'done') {
      const transcript = await recall.bot.getTranscript({ 
        id: botId,
        enhanced_diarization: true 
      });
      
      const intelligence = await recall.bot.getBotIntellienge({ id: botId });
      
      // Save to local storage
      fs.writeFileSync(
        `recording-${botId}.json`,
        JSON.stringify({ transcript, intelligence }, null, 2)
      );
      
      console.log('Data saved locally');
      
      // Delete media from Recall
      await recall.bot.deleteBotMedia({ id: botId });
      console.log('Media deleted from Recall');
    }
    
  } catch (error) {
    console.error('Error:', error);
    
    // Clean up on error
    if (botId) {
      try {
        await recall.bot.leaveCall({ id: botId });
        await recall.bot.deleteBotMedia({ id: botId });
      } catch (cleanupError) {
        console.error('Cleanup error:', cleanupError);
      }
    }
  }
}

recordAndCleanup('https://zoom.us/j/123456789');

Build docs developers (and LLMs) love