# All MeetingBaas documentation content

This contains all documentation across Meeting BaaS systems. Each section below is from a different part of the documentation.

## Alerts

Configure alerts to monitor bot operations, resource usage, and webhook delivery health

### Source: ./content/docs/api-v2/alerts.mdx


Alerts notify you when something goes wrong or when resource thresholds are reached. Configure alert rules from your dashboard to receive email notifications and HTTP callbacks in real-time.

## Overview

The alerts system monitors two categories of events:

- **Operational alerts** detect errors during bot operations — failed joins, recording failures, crashes, and more
- **Threshold alerts** monitor resource metrics like token balance, daily bot usage, and calendar connections

Each alert rule defines what to monitor, when to trigger, how to notify you, and how often.

## Creating an Alert Rule

Navigate to **Alerts** in your dashboard to create a new rule. Each rule requires:

1. **Alert type** — what to monitor (see [Alert Types](#alert-types) below)
2. **Threshold** — the value that triggers the alert
3. **Delivery channels** — where to send notifications (email, callback, or both)
4. **Cooldown** — minimum time between consecutive alerts (1–1440 minutes, default: 15 minutes)

At least one delivery channel (email or callback) is required.

## Alert Types

### Operational Alerts

Operational alerts count error occurrences and fire when the count reaches your configured threshold. The same cooldown window is used for both counting events and suppressing repeated alerts after delivery (see [Cooldown and Suppression](#cooldown-and-suppression) below). For example, "alert me when bot crashes reach 3 occurrences within 15 minutes."

| Alert Type | Description | Common Triggers |
|------------|-------------|-----------------|
| Bot Join Failed | Bot could not join the meeting | Invalid meeting URL, meeting ended before bot joined, login required, waiting for host timeout, bot not accepted |
| Recording Failed | Bot joined but could not record | Recording rights not granted, recording start timeout, host cannot grant permission |
| Zoom Credential Error | Zoom authentication failure | Invalid JWT token, SDK auth failed, access token error |
| Bot Crash | Bot process exited unexpectedly | Out of memory, force killed, general error |
| Transcription Failed | Transcription service error | Provider returned an error after recording completed |
| Calendar Sync Error | Calendar synchronization failure | OAuth token expired, provider API error |
| Meet Login Unavailable | No authenticated Google Meet login slot was available | Round-robin pool saturated, no active login matched, bot failed with `MEET_LOGIN_UNAVAILABLE` |
| Webhook Delivery Exhausted | All webhook retry attempts failed | Endpoint unreachable, returning errors consistently |

<Callout type="info">
The **Webhook Delivery Exhausted** alert is particularly important — each time a webhook message exhausts all retry attempts, the alert metric increments. If cooldown is active, additional exhausted deliveries are aggregated into the `suppressed_count` and reported when the next alert fires. This gives you visibility into endpoint health well before the 5-day auto-disable threshold. See [Webhook Auto-Disable](/docs/api-v2/webhooks#auto-disable) for details.
</Callout>

### Threshold Alerts

Threshold alerts fire when a resource metric crosses a configured boundary. They support both `>=` (greater than or equal) and `<=` (less than or equal) operators.

| Alert Type | Description | Example Use Case |
|------------|-------------|------------------|
| Daily Bot Cap | Number of bots created today | "Alert me when daily bots reach 90% of my plan limit" |
| Token Balance | Current token balance | "Alert me when tokens drop below 1000" |
| Calendar Connections | Number of connected calendars | "Alert me when calendar connections reach my plan limit" |
| Meet Login Utilization | Concurrency used across your Google Meet login pool | "Alert me when meet login utilization reaches 70%" |

### Meet login alerts

If you run [authenticated Google Meet bots](/docs/api-v2/authenticated-bots/meet), two alert types help you keep the authentication pool healthy. Configure these **before** you rely on the pool in production — together they warn you as it fills up and tell you the moment it overflows.

| Alert | Category | What it tracks | Recommended setup |
|-------|----------|----------------|-------------------|
| **Meet Login Utilization** | Threshold | The percentage of your pool's concurrent capacity in use (`utilization_pct` from [`GET /v2/meet-logins/utilization`](/docs/api-v2/reference/meet-logins/getMeetLoginUtilization)). | `>= 70%`, so you have time to add logins before saturation. |
| **Meet Login Unavailable** | Operational | Bots that failed to get an authenticated login slot because the pool was saturated (`MEET_LOGIN_UNAVAILABLE`). | `>= 1` occurrence, to hear about saturation immediately. |

Use the **Meet Login Utilization** alert as the early warning and **Meet Login Unavailable** as the safety net. When either fires, add more meet logins to the pool (each login adds capacity) or reduce concurrent bot dispatch. See [Sending Authenticated Bots → Monitoring pool utilization](/docs/api-v2/authenticated-bots/meet/sending-authenticated-bots#monitoring-pool-utilization) for how capacity and round-robin assignment work.

## Delivery Channels

Each alert rule can use one or both delivery channels:

### Email

Configure up to 10 email recipients per rule. When an alert fires, all recipients receive an email containing:

- Alert rule name
- Current metric value and threshold
- Number of suppressed alerts during the cooldown period (if any)
- Direct link to the dashboard

### Callback

Configure an HTTP endpoint to receive alert notifications programmatically. Callbacks are sent as `POST` requests with a JSON payload:

```json
{
  "alert_rule_id": "550e8400-e29b-41d4-a716-446655440000",
  "alert_rule_name": "Bot Crash Monitor",
  "alert_type": "bot_crash",
  "category": "operational",
  "triggered_at": "2025-01-15T10:30:00.000Z",
  "current_value": 3,
  "threshold_value": 3,
  "suppressed_count": 0,
  "message": "Bot Crash: 3 occurrence(s) reached threshold of 3"
}
```

If you configure a `secret` on the callback, it is included as the `x-mb-alert-secret` header for verification.

**Callback retry behavior:**
- 4 total attempts (initial + 3 retries)
- Retry delays: 1 second, 5 seconds, 15 seconds
- Only retries on network errors and 5xx server responses
- Does **not** retry on 4xx client errors
- 30-second request timeout

## Cooldown and Suppression

The cooldown period (default: 15 minutes) serves a dual purpose — it is both the **counting window** for accumulating events toward a threshold and the **suppression window** that prevents repeated alerts after delivery.

**How it works:**

1. Events are counted within a rolling window equal to the cooldown period
2. When the count reaches the threshold, the alert fires and delivery begins
3. After delivery, the cooldown starts — additional events during this period are **counted but not delivered**
4. When the cooldown expires and the next trigger occurs, the alert fires again with a `suppressed_count` showing how many events were batched during the suppression period

<Callout type="info">
The system intentionally uses the same time window for both event accumulation (pre-trigger) and post-delivery suppression. For example, with a threshold of `>= 3` and a 15-minute cooldown, the system counts occurrences within a 15-minute window. Once 3 events are reached and the alert fires, the same 15-minute period becomes the suppression window.
</Callout>

**Example:** You configure a "Bot Join Failed" alert with threshold `>= 1` and a 15-minute cooldown.

- `10:00` — First bot join fails → threshold met, alert fires immediately
- `10:02` — Second failure → suppressed (cooldown active)
- `10:08` — Third failure → suppressed (cooldown active)
- `10:16` — Fourth failure → cooldown expired, alert fires with `suppressed_count: 2`

## Testing Alerts

Use the **Test** button on any alert rule to send a test notification to all configured delivery channels. This verifies that your email addresses are correct and your callback endpoint is reachable.

## Alert History

Each alert rule maintains a delivery history accessible from the rule's detail page. History entries include:

- When the alert fired
- Current value at the time of trigger
- Number of suppressed events
- Delivery status for each channel (success or failure with error details)

## Limits

| Setting | Limit |
|---------|-------|
| Alert rules per team | 50 |
| Email recipients per rule | 10 |
| Cooldown range | 1–1440 minutes (24 hours) |
| Minimum threshold for operational alerts | 1 occurrence |


---

## API Keys & Permissions

Learn about API keys, permissions, and access control in Meeting BaaS v2

### Source: ./content/docs/api-v2/api-keys.mdx


API keys are used to authenticate requests to the Meeting BaaS v2 API. Each API key is associated with a team and has specific permissions that determine which endpoints can be accessed.

## API Key Authentication

All v2 API requests must include your API key in the request header:

```http
x-meeting-baas-api-key: YOUR-API-KEY
```

## Permission Types

Meeting BaaS v2 supports two permission types for API keys:

### Full Access

API keys with **Full Access** can perform all operations on all endpoints:

- Create, list, view, update, and delete bots
- Create, list, view, update, and delete scheduled bots
- Manage calendar connections and events
- Schedule and manage calendar bots
- Use all batch operation endpoints
- Manage webhooks and callbacks

**Use cases:**
- Complete application integration
- Administrative operations
- Calendar management
- Webhook configuration
- Data management and cleanup

### Sending Access

API keys with **Sending Access** are designed for bot creation only. They can only send POST requests to bot creation endpoints:

**Allowed endpoints:**
- `POST /v2/bots` - Create a bot
- `POST /v2/bots/batch` - Create multiple bots
- `POST /v2/bots/scheduled` - Schedule a bot
- `POST /v2/bots/scheduled/batch` - Schedule multiple bots
- `POST /v2/calendars/:calendar_id/bots` - Schedule a bot for a calendar event

**Not allowed:**
- Any GET requests (including listing bots, getting bot details, checking status)
- Any PATCH or DELETE requests
- Calendar management endpoints
- Webhook or callback management

**Use cases:**
- Dedicated bot creation services
- Third-party integrations that only need to send bots
- Limited-scope applications
- Security isolation (prevents accidental data deletion or viewing)

## Rate Limiting

Rate limits are applied per team (not per API key). The rate limit determines how many requests per second your team can make. GET requests are not rate limited.

Default rate limits vary by plan, but can be customized for enterprise customers.

## Daily Bot Cap

Each team has a daily bot creation limit (e.g., 75 bots/day for pay-as-you-go, 300 for pro). This limit is checked before creating each bot. If the limit is reached, subsequent bot creation requests will fail with a `429 Too Many Requests` error.

The daily bot cap resets every 24 hours based on when bots were created.

## Best Practices

1. **Use separate API keys for different environments** (development, staging, production)
2. **Use Sending Access for public-facing services** that only need to create bots
3. **Rotate API keys regularly** for security
4. **Store API keys securely** - never commit them to version control
5. **Monitor your daily bot cap** to avoid hitting limits

## Error Responses

If an API key lacks permission for an endpoint, you'll receive a `403 Forbidden` response:

```json
{
  "success": false,
  "error": "Forbidden",
  "code": "FST_ERR_FORBIDDEN",
  "statusCode": 403,
  "message": "Forbidden"
}
```



---

## Artifacts

Complete guide to all artifacts generated by Meeting BaaS v2 bots

### Source: ./content/docs/api-v2/artifacts.mdx


Meeting BaaS v2 generates various artifacts for each bot, including video recordings, audio files, transcriptions, and diarization data. All artifacts are stored securely and accessed via presigned URLs that are valid for 4 hours.

## Overview

When a bot completes recording a meeting, it generates several artifacts that you can access:

- **Video**: MP4 video recording (when video recording is enabled)
- **Audio**: FLAC audio recording (always generated)
- **Transcription**: Standardized transcription with accurate timestamps
- **Raw Transcription**: Provider-specific transcription with advanced features
- **Diarization**: Speaker identification and timing data
- **Chat Messages**: JSON file containing all chat messages exchanged during the meeting

All artifacts are accessed via presigned URLs provided in bot details responses and webhook payloads.

## Video Artifact

The video artifact contains the complete video recording of the meeting.

**Format**: MP4 video file

**When Available**: Only when `recording_mode` is NOT `audio_only`

**Use Cases**:

- Providing users with raw video recording of meetings
- Video playback and review
- Integration with video analysis tools
- Archival purposes
- Creating video summaries or highlights

**Access**: Available via the `video` field in bot details and webhook payloads. Returns `null` if video recording was not enabled or if the bot's data has been deleted.

**Signed URL**: Valid for 4 hours

## Audio Artifact

The audio artifact contains the complete audio recording of the meeting.

**Format**: FLAC audio file

**When Available**: Always generated for every bot (regardless of recording mode)

**Use Cases**:

- Audio-only playback
- Integration with external transcription workflows
- Audio analysis and processing
- Backup audio source
- Creating audio-only versions of meetings

**Access**: Available via the `audio` field in bot details and webhook payloads. Returns `null` if the bot's data has been deleted.

**Signed URL**: Valid for 4 hours

## Transcription Artifact

The transcription artifact provides a standardized, processed transcription with accurate timestamps and speaker identification.

**Format**: JSON file

**Structure**:

```json
{
  "bot_id": "123e4567-e89b-12d3-a456-426614174000",
  "provider": "gladia",
  "result": {
    "utterances": [
      {
        "text": "Hello everyone, welcome to the meeting",
        "language": "en",
        "start": 0.5,
        "end": 2.1,
        "confidence": 0.95,
        "channel": 0,
        "words": [
          {
            "word": "Hello",
            "start": 0.5,
            "end": 0.8,
            "confidence": 0.98
          },
          {
            "word": "everyone",
            "start": 0.8,
            "end": 1.2,
            "confidence": 0.96
          }
        ],
        "speaker": "John Doe"
      }
    ],
    "languages": ["en"],
    "total_utterances": 150,
    "total_duration": 3600.5
  },
  "created_at": "2025-01-15T11:00:00Z"
}
```

**Utterance Fields**:

- `text`: The transcribed text for this utterance
- `language`: ISO 639-1 language code (e.g., "en", "es")
- `start`: Start time in seconds (floating point)
- `end`: End time in seconds (floating point)
- `confidence`: Confidence score (0.0 to 1.0)
- `channel`: Audio channel number
- `words`: Array of word-level timestamps
- `speaker`: Real speaker name

**Word Structure**:

- `word`: The word text
- `start`: Start time in seconds
- `end`: End time in seconds
- `confidence`: Confidence score (0.0 to 1.0)

**Key Features**:

- Speaker names are real names (not numeric IDs)
- All utterances sorted chronologically
- Accurate timestamps for the entire meeting
- Word-level timestamps for precise alignment

**Use Cases**:

- Standard transcript display with accurate timestamps
- Building transcript viewers/players
- Search and indexing
- Meeting summaries and analysis
- Creating subtitles or captions
- Speaker analytics and talk time analysis

**Access**: Available via the `transcription` field in bot details and webhook payloads. Returns `null` if transcription was not enabled or if the bot's data has been deleted.

**Signed URL**: Valid for 4 hours

## Raw Transcription Artifact

The raw transcription artifact contains the complete, unmodified response from the transcription provider. This includes all provider-specific features and metadata.

**Format**: JSON file

**Structure**: Varies by transcription provider

**Important Notes**:

- Structure varies by provider
- Timestamps may not be accurate due to internal chunking processes
- Speaker IDs are numeric (not real names)
- Contains provider-specific features (summarization, LLM responses, etc.)

**Use Cases**:

- Accessing provider-specific features (summarization, LLM prompts, etc.)
- Custom transcription processing workflows
- Accessing full transcript text without time-matched utterances
- Integration with provider-specific APIs
- Accessing advanced features like translations, sentiment analysis, and named entity recognition

**Limitations**:

- NOT suitable for transcript display (use `transcription` artifact instead)
- Timestamps may not be accurate
- Speaker IDs are numeric, not names

**Access**: Available via the `raw_transcription` field in bot details and webhook payloads. Returns `null` if transcription was not enabled or if the bot's data has been deleted.

**Signed URL**: Valid for 4 hours

### Provider-Specific Structures

#### Gladia

The Gladia raw transcription combines all audio chunk transcriptions into a single file:

```json
{
  "bot_id": "123e4567-e89b-12d3-a456-426614174000",
  "transcriptions": [
    {
      "metadata": {
        "audio_duration": 1800.5,
        "number_of_distinct_channels": 1,
        "billing_time": 1800.5,
        "transcription_time": 22.1
      },
      "transcription": {
        "full_transcript": "Hello everyone, welcome to the meeting...",
        "languages": ["en"],
        "utterances": [
          {
            "start": 0.5,
            "end": 2.1,
            "confidence": 0.95,
            "channel": 0,
            "speaker": 0,
            "words": [
              {
                "word": "Hello",
                "start": 0.5,
                "end": 0.8,
                "confidence": 0.98
              }
            ],
            "text": "Hello everyone",
            "language": "en"
          }
        ]
      },
      "summarization": {
        "summary": "Meeting discussed Q4 goals and team alignment..."
      },
      "translation": {
        "es": {
          "utterances": [...]
        }
      },
      "audio_to_llm": {
        "response": "The meeting covered..."
      }
    },
    {
      "metadata": {
        "audio_duration": 1800.0,
        "number_of_distinct_channels": 1,
        "billing_time": 1800.0,
        "transcription_time": 23.0
      },
      "transcription": {
        "full_transcript": "Let's continue with the next topic...",
        "languages": ["en"],
        "utterances": [...]
      }
    }
  ],
  "created_at": "2025-01-15T11:00:00Z"
}
```

**Structure**:

- `bot_id`: The bot UUID
- `transcriptions`: Array of transcription payloads, one per audio chunk
- `created_at`: ISO 8601 timestamp when the combined transcription was created

Each element in the `transcriptions` array contains the Gladia transcription payload structure with:

**Additional Fields** (based on custom parameters):

- `summarization`: AI-generated meeting summary
- `translation`: Translated transcriptions by target language
- `sentiment_analysis`: Sentiment scores for utterances
- `named_entity_recognition`: Extracted entities (names, organizations, locations)
- `audio_to_llm`: LLM prompt responses
- `moderation`: Content moderation results
- `chapterization`: Automatic meeting chapters
- And more based on your configuration

**Reference**: For complete documentation on Gladia's response structure and all available fields, see the [Gladia API Documentation](https://docs.gladia.io/api-reference/v2/pre-recorded/callback/success).

#### Additional Providers

Meeting BaaS also supports Deepgram, AssemblyAI, Speechmatics, and Soniox (plus ElevenLabs for real-time streaming). When you use a non-Gladia provider, the raw transcription artifact mirrors that provider's native response structure. Refer to the selected provider's documentation for the exact shape of its output.

## Diarization Artifact

The diarization artifact contains speaker identification and timing information, useful for custom transcription workflows.

**Format**: JSONL file

**Structure** (Zoom example):

```jsonl
{"speaker": "John Doe", "start_time": 0.5, "end_time": 5.2, "user_id": 123, "lang": "en"}
{"speaker": "Jane Smith", "start_time": 5.3, "end_time": 10.1, "user_id": 456, "lang": "en"}
```

**Structure** (Google Meet/Teams example):

```jsonl
{"speaker": "John Doe", "start_time": 0.5, "end_time": 5.2, "user_id": 1}
{"speaker": "Jane Smith", "start_time": 5.3, "end_time": 10.1, "user_id": 2}
```

**Fields**:

- `speaker`: Real speaker name
- `start_time`: Start time in seconds
- `end_time`: End time in seconds
- `user_id`: Platform or Assigned user ID - (Zoom and Google Meet only, optional)
- `lang`: Language code (Zoom only, optional)

**Use Cases**:

- Custom transcription workflows
- Speaker identification and analysis
- Building custom transcript processing
- Integration with external diarization tools
- Maintaining speaker-to-transcript relationships
- Talk time analysis per speaker

**Access**: Available via the `diarization` field in bot details and webhook payloads. Returns `null` if diarization data is not available or if the bot's data has been deleted.

**Signed URL**: Valid for 4 hours

**Platform Differences**:

- **Zoom**: Includes `user_id` (platform user ID) and optional `lang` fields in each segment
- **Google Meet/Teams**: Includes `user_id` (assigned user ID) when available. The assigned ID attempts to remain consistent even if a participant rejoins the meeting.

## Chat Messages Artifact

The chat messages artifact contains all chat messages exchanged during the meeting, including messages from participants and messages sent by the bot via the [send chat message](/api-v2/reference/bots/sendChatMessage) endpoint.

**Format**: JSON file

**When Available**: Only when chat messages were exchanged during the meeting. If no messages were sent or received, this artifact will not be generated.

**Structure**:

```json
[
  {
    "message_id": "spaces/XxM_aNTpzGkB/messages/1773539019302815",
    "sender_name": "John Doe",
    "sender_id": 2,
    "text": "Hi everyone!",
    "timestamp": "2025-01-15T10:30:15.359Z"
  },
  {
    "message_id": "71149604-eb75-433c-91bc-6a3c02defa94",
    "sender_name": "Meeting Bot",
    "sender_id": 1,
    "text": "Hello! How can I help?",
    "timestamp": "2025-01-15T10:30:28.456Z"
  }
]
```

**Fields**:

- `message_id`: Unique identifier for the message (format varies by platform)
- `sender_name`: Display name of the message sender
- `sender_id`: Participant ID of the sender. For Google Meet, this is the assigned sequential ID. For Zoom, this is the SDK user ID. May be `null` for Teams or if the sender could not be resolved to a participant.
- `text`: Text content of the message (HTML tags stripped for Teams messages)
- `timestamp`: ISO 8601 timestamp of when the message was sent or received

**Note on timestamps**: The `timestamp` field in the artifact represents when the message was sent or received in the meeting. This differs from the `sent_at` field in the `bot.chat_message` webhook, which represents when the webhook was dispatched by the server.

**Real-Time Events**: In addition to the artifact, each chat message triggers a `bot.chat_message` webhook event in real-time as messages are received during the meeting. The artifact provides a complete record of all messages for post-meeting access.

**Use Cases**:

- Post-meeting review of chat discussions
- Capturing action items and links shared in chat
- Audit trail of meeting communications
- Integration with note-taking and project management tools
- Correlating chat messages with transcript timestamps

**Access**: Available via the `chat_messages` field in bot details and webhook payloads. Returns `null` if no chat messages were exchanged or if the bot's data has been deleted.

**Signed URL**: Valid for 4 hours

**Platform Differences**:

- **Google Meet**: `sender_id` is an auto-assigned sequential participant ID (same as in diarization). Since Google Meet does not provide native participant IDs, these are generated internally and may be `null` in some cases.
- **Microsoft Teams**: `sender_id` is always `null` (Teams does not provide a participant ID mapping for chat senders). Sender names are resolved from the platform's display name field.
- **Zoom**: `sender_id` is the Zoom SDK user ID (numeric, e.g., `16778240`). Both received messages and bot-sent messages include the SDK user ID.

## Additional Response Fields

### Transcription IDs

**Type**: `string[] | null`

**Description**: Array of transcription job IDs from the transcription provider

**Use Cases**:

- BYOK (Bring Your Own Key) users maintaining relationships with transcription providers
- Accessing provider-specific endpoints using these IDs
- Tracking transcription jobs across provider APIs
- Debugging and support
- Correlating transcription errors with specific provider jobs

**Example**:

```json
{
  "transcription_ids": ["gladia-job-12345", "gladia-job-12346"],
  "transcription_provider": "gladia"
}
```

**Access**: Available via the `transcription_ids` field in bot details and webhook payloads. Returns `null` if transcription was not enabled.

### Participants Array

**Type**: `Array<{name: string, id: number | null, display_name?: string, profile_picture?: string}>`

**Description**: List of all participants who joined the meeting

**Structure**:

```json
{
  "participants": [
    {
      "name": "John Doe",
      "id": 1,
      "display_name": "John",
      "profile_picture": "https://lh3.googleusercontent.com/..."
    },
    {
      "name": "Jane Smith",
      "id": 2
    }
  ]
}
```

**Fields**:

- `name`: Participant full name
- `id`: Platform or assigned user ID (null if unavailable)
- `display_name`: Display name shown in UI (optional, only present if different from `name`)
- `profile_picture`: Profile picture URL (optional, only present when available)

**Use Cases**:

- Participant tracking and analytics
- Meeting attendance reports
- Integration with CRM/HR systems
- Building participant lists for meeting summaries

**Access**: Available via the `participants` field in bot details and webhook payloads.

### Speakers Array

**Type**: `Array<{name: string, id: number | null, display_name?: string, profile_picture?: string}>`

**Description**: List of speakers identified in the meeting (subset of participants who spoke)

**Structure**: Same as participants array

**Fields**:

- `name`: Speaker full name
- `id`: Platform or assigned user ID (null if unavailable)
- `display_name`: Display name shown in UI (optional, only present if different from `name`)
- `profile_picture`: Profile picture URL (optional, only present when available)

**Use Cases**:

- Speaker analytics
- Talk time analysis
- Identifying active participants
- Building speaker-focused meeting summaries

**Access**: Available via the `speakers` field in bot details and webhook payloads.

## Artifact Access

All artifacts are accessed via presigned URLs that are valid for 4 hours from the time they are generated.

**Where to Find Artifacts**:

- `GET /v2/bots/{bot_id}` response - All artifact URLs in the response
- `bot.completed` webhook payload - All artifact URLs when bot completes
- Bot callbacks - Same URLs as webhook payloads

**Important Notes**:

- URLs expire after 4 hours - download artifacts promptly
- If `artifacts_deleted: true`, all artifact URLs will be `null`
- Artifacts are stored securely with data retention tags
- Download and store artifacts in your own storage for long-term access

## Data Retention and Security

With security built into every aspect of v2, data retention is as well. This approach severely limits data exposure by automatically removing artifacts after a specified retention period.

### Retention by Plan

Data retention periods vary by your API plan:

- **Pay-as-you-go**: 3 days
- **Pro**: 7 days
- **Scale**: 14 days
- **Enterprise**: 30 days

### Automatic Deletion

A background job automatically deletes artifacts that have exceeded the retention period based on your plan. This ensures data is not stored longer than necessary and minimizes security exposure.

### Manual Deletion

We recommend users take ownership of their data by using the `DELETE /v2/bots/{bot_id}/delete-data` endpoint to manually delete artifacts once they've been saved at your end.

**What Gets Deleted**:

- All artifacts from S3 (video, audio, transcription, diarization, chat messages, screenshots)
- Optionally deletes transcription data from the transcription provider (default: `true`)
- Sets `artifacts_deleted: true` flag
- Ensures complete data scrubbing from our system

**Transcription Provider Deletion**: When using the delete endpoint with `delete_from_provider=true` (default), we also delete the transcription data from the transcription provider's servers (e.g., Gladia). This ensures complete data removal across all systems.

### Extended Retention

If you have a specific reason for needing data retained longer than your plan's default retention period, please [contact our support team](https://dashboard.meetingbaas.com/support-center) and we'll discuss how we can best support your needs.

## Platform-Specific Notes

### Zoom

- JSONL diarization format
- User ID mapping in diarization data
- Multi-speaker support with channel-based audio

### Google Meet

- JSONL diarization format (same as Zoom)
- Network-based speaker detection for improved accuracy
- Diarization segments include `speaker`, `start_time`, `end_time`, and `user_id` (assigned user ID)
- Single audio file processing
- **Diarization Accuracy**: 
  - Uses network-based detection which provides more accurate speaker identification
  - May have slight inaccuracies in timestamp alignment (typically less than 1 second)
  - Our system uses a statistical analysis approach to improve accuracy:
    - Matches transcription utterances to diarization segments using a ±1 second time window
    - Samples 30% of utterances (minimum 50, maximum 200 samples per speaker) for statistical significance
    - Uses frequency-based matching: the speaker with the highest match count within the time window is selected
    - Calculates confidence scores based on match frequency
    - This approach compensates for any timing discrepancies by finding the most likely speaker match statistically
  - For the most accurate timestamps, use the `transcription` artifact (output transcription) which applies timestamp offsets to account for chunk boundaries and provides accurate timing across the entire meeting

### Microsoft Teams

- JSONL diarization format (same as Zoom)
- UI-based speaker detection
- Diarization segments only include `speaker`, `start_time`, and `end_time` (no `user_id` or `lang`)
- Single audio file processing

## Best Practices

1. **Download Promptly**: Presigned URLs expire after 4 hours - download artifacts as soon as they're available
2. **Store Long-Term**: If you need artifacts long-term, download and store them in your own storage
3. **Use Webhooks**: Set up webhooks to receive artifact URLs automatically when bots complete
4. **Delete When Done**: Use the delete endpoint to remove data when you no longer need it
5. **Use Appropriate Artifacts**: Use `transcription` for display, `raw_transcription` for advanced features
6. **Track Transcription IDs**: For BYOK users, use `transcription_ids` to correlate with provider jobs

## Examples

### Accessing Artifacts from Bot Details

```bash
curl -X GET "https://api.meetingbaas.com/v2/bots/BOT-ID" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"
```

**Response**:

```json
{
  "success": true,
  "data": {
    "bot_id": "123e4567-e89b-12d3-a456-426614174000",
    "status": "completed",
    "video": "https://s3.amazonaws.com/.../video.mp4",
    "audio": "https://s3.amazonaws.com/.../output.flac",
    "transcription": "https://s3.amazonaws.com/.../output_transcription.json",
    "raw_transcription": "https://s3.amazonaws.com/.../raw_transcription.json",
    "diarization": "https://s3.amazonaws.com/.../diarization.jsonl",
    "chat_messages": "https://s3.amazonaws.com/.../chat_messages.json",
    "participants": [
      { "name": "John Doe", "id": 1, "display_name": "John", "profile_picture": "https://lh3.googleusercontent.com/..." },
      { "name": "Jane Smith", "id": 2 }
    ],
    "speakers": [
      { "name": "John Doe", "id": 1, "display_name": "John", "profile_picture": "https://lh3.googleusercontent.com/..." },
      { "name": "Jane Smith", "id": 2 }
    ],
    "transcription_ids": ["gladia-job-12345"],
    "transcription_provider": "gladia"
  }
}
```

### Accessing Artifacts from Webhook

When a bot completes, you'll receive a webhook with all artifact URLs:

```json
{
  "event": "bot.completed",
  "data": {
    "bot_id": "123e4567-e89b-12d3-a456-426614174000",
    "video": "https://s3.amazonaws.com/.../video.mp4",
    "audio": "https://s3.amazonaws.com/.../output.flac",
    "transcription": "https://s3.amazonaws.com/.../output_transcription.json",
    "raw_transcription": "https://s3.amazonaws.com/.../raw_transcription.json",
    "diarization": "https://s3.amazonaws.com/.../diarization.jsonl",
    "chat_messages": "https://s3.amazonaws.com/.../chat_messages.json",
    "transcription_ids": ["gladia-job-12345"],
    "transcription_provider": "gladia"
  }
}
```

## Frequently Asked Questions

<Accordions>
  <Accordion title="How does Meeting BaaS create transcript chunks?">
    Meeting BaaS uses different chunking strategies depending on the meeting platform:

    **Zoom Bot:**

    - Creates **speaker-based chunks** in real-time as audio is received from the Zoom SDK
    - Each chunk is associated with a specific `user_id` (speaker)
    - Chunks are created automatically based on SDK audio events (approximately every 5-6 minutes per speaker)
    - Minimum chunk size: 100KB (smaller chunks are skipped)
    - Each chunk maintains speaker identity throughout, enabling perfect diarization

    **Google Meet/Teams Bot:**

    - Creates **time-based chunks** after the meeting recording is complete
    - The entire meeting audio is first recorded, then split into chunks using FFmpeg
    - Maximum chunk duration: 2 hours (7,200 seconds) per chunk
    - Chunks are created sequentially based on time intervals, not speaker identity
    - This approach allows processing of very long meetings while respecting transcription provider limits

    The chunking strategy ensures optimal transcription quality while managing file sizes and processing time efficiently.

  </Accordion>

  <Accordion title="How accurate are the diarization timestamps in the diarization file?">
    Diarization timestamp accuracy varies by platform:

    **Zoom:**

    - **Highly accurate** - Uses Zoom's native diarization with perfect speaker-to-user mapping
    - Timestamps are precise and directly tied to Zoom's audio stream
    - Each segment includes `user_id` for reliable speaker identification

    **Google Meet:**

    - Uses network-based speaker detection for improved accuracy
    - May have slight inaccuracies in timestamp alignment (typically less than 1 second)
    - See the [Google Meet section](#google-meet) above for detailed information about our statistical analysis approach

    **Microsoft Teams:**

    - **May have slight inaccuracies** - Uses UI-based speaker detection which can introduce latency
    - UI diarization relies on visual indicators (speaker highlighting) which may lag behind actual speech by 1-2 seconds

    For the most accurate timestamps, use the `transcription` artifact (output transcription) which applies timestamp offsets to account for chunk boundaries and provides accurate timing across the entire meeting.

  </Accordion>

  <Accordion title="How does Google Meet maintain consistent user_id across rejoins?">
    Google Meet uses a stable identifier system to assign `user_id` values that remain consistent even when participants rejoin:

    - **Identification Method**: Creates a unique hash based on the participant's profile picture URL (preferred) or full name
    - **Consistency**: The `user_id` will remain the same as long as:
      - The participant's profile picture URL doesn't change between rejoins (highly unlikely)
      - Or, if no profile picture is available, the participant's name remains unchanged
    - **Why This Works**: Profile picture URLs are unique per Google account and rarely change, making them a stable identifier. If a profile picture isn't available, the full name serves as a fallback identifier.

    This approach ensures that the same participant receives the same `user_id` throughout the meeting, even if they temporarily leave and rejoin, making it easier to track speaker continuity in your analysis.

  </Accordion>

<Accordion title="How long are signed URLs valid?">
  All artifact signed URLs are valid for **4 hours** from the time they are
  generated. After 4 hours, the URLs expire and you'll need to fetch new URLs
  from the bot details endpoint or webhook.
</Accordion>

<Accordion title="What happens if I don't download artifacts before they expire?">
  If a signed URL expires, you can retrieve new signed URLs by calling `GET
  /v2/bots/{bot_id}`. The endpoint will generate fresh signed URLs that are
  valid for another 4 hours. However, if the artifacts have been deleted (either
  manually or after the retention period), the URLs will return `null`.
</Accordion>

  <Accordion title="What's the difference between transcription and raw_transcription?">
    - **`transcription`** (output transcription): A standardized, processed transcription with accurate timestamps, real speaker names, and word-level timestamps. This is the recommended artifact for displaying transcripts to users.

    - **`raw_transcription`**: The complete, unmodified response from the transcription provider. Contains provider-specific features (summarization, LLM responses, translations, etc.) but timestamps may not be accurate due to chunking, and speaker IDs are numeric rather than names.

    Use `transcription` for display purposes and `raw_transcription` when you need access to provider-specific advanced features.

  </Accordion>

  <Accordion title="Why are some artifact URLs null?">
    Artifact URLs can be `null` for several reasons:

    - **Video**: `null` when `recording_mode` is `audio_only` or if the artifact has been deleted
    - **Transcription/Raw Transcription**: `null` when transcription was not enabled or if artifacts have been deleted
    - **Diarization**: `null` when diarization data is not available or has been deleted
    - **Chat Messages**: `null` when no chat messages were exchanged during the meeting or if artifacts have been deleted
    - **All artifacts**: `null` when `artifacts_deleted: true` (data has been manually deleted or exceeded retention period)

  </Accordion>

  <Accordion title="How do I know when artifacts are ready?">
    Artifacts are ready when the bot status is `completed`. You can:

    1. **Poll the bot details endpoint**: `GET /v2/bots/{bot_id}` - Check the `status` field
    2. **Use webhooks**: Set up a `bot.completed` webhook to receive artifact URLs automatically when the bot finishes
    3. **Check artifact URLs**: When `status: "completed"`, all available artifact URLs will be populated (non-null)

  </Accordion>

<Accordion title="Can I regenerate signed URLs after they expire?">
  Yes! Simply call `GET /v2/bots/{bot_id}` again to get fresh signed URLs. As
  long as the artifacts haven't been deleted, you'll receive new 4-hour valid
  URLs.
</Accordion>

  <Accordion title="What happens to artifacts after the retention period?">
    Artifacts are automatically deleted by a background job after your plan's retention period expires:

    - **Pay-as-you-go**: 3 days
    - **Pro**: 7 days
    - **Scale**: 14 days
    - **Enterprise**: 30 days

    After deletion, all artifact URLs will return `null` and `artifacts_deleted` will be `true`. We recommend downloading and storing artifacts in your own storage if you need long-term access.

  </Accordion>
</Accordions>

## Next Steps

- Learn about [Getting the Data](/docs/api-v2/getting-started/getting-the-data) to access artifacts
- Set up [Webhooks](/docs/api-v2/webhooks) to receive artifact URLs automatically
- Explore [Transcription](/docs/api-v2/transcription) features and custom parameters
- Check the [API Reference](/docs/api-v2/reference) for complete endpoint documentation


---

## Batch Operations

Learn how to create multiple bots in a single request

### Source: ./content/docs/api-v2/batch-operations.mdx


Batch operations allow you to create multiple bots in a single API request. This is useful for bulk operations and reduces the number of API calls needed.

## Creating Multiple Bots

To create multiple bots at once, use the batch endpoint:

```bash
curl -X POST "https://api.meetingbaas.com/v2/bots/batch" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '[
           {
             "meeting_url": "https://meet.google.com/abc-defg-hij",
             "bot_name": "Bot 1",
             "recording_mode": "speaker_view"
           },
           {
             "meeting_url": "https://zoom.us/j/123456789",
             "bot_name": "Bot 2",
             "recording_mode": "gallery_view"
           }
         ]'
```

## Response Format

The batch endpoint returns a response with both successful and failed items:

```json
{
  "success": true,
  "data": {
    "success": [
      {
        "index": 0,
        "bot_id": "123e4567-e89b-12d3-a456-426614174000",
        "extra": null
      }
    ],
    "errors": [
      {
        "index": 1,
        "code": "INSUFFICIENT_TOKENS",
        "message": "Insufficient tokens. Available: 0, Required: 0.5",
        "details": null,
        "extra": null
      }
    ]
  }
}
```

## Partial Success

Batch operations support **partial success**. This means:

- Some bots may be created successfully while others fail
- Each item is processed independently
- Errors for one item don't prevent other items from being processed
- The response includes both successful and failed items with their original indices

## Error Handling

Each item in the batch is validated and processed individually. Common errors include:

- `INSUFFICIENT_TOKENS`: Not enough tokens to create the bot
- `DAILY_BOT_CAP_REACHED`: Daily bot creation limit reached
- `BOT_ALREADY_EXISTS`: A bot already exists for this meeting URL (if `allow_multiple_bots` is `false`)
- `INVALID_MEETING_PLATFORM`: Could not determine meeting platform from URL
- `VALIDATION_ERROR`: Request validation failed

## Use Cases

Batch operations are ideal for:

- Bulk bot creation for multiple meetings
- Scheduled bot creation for recurring events
- Migrating bots from another system
- Creating test bots in bulk

## Batch Size Limits

- **Minimum**: 1 bot per batch
- **Maximum**: 100 bots per batch

If you exceed 100 items, the request will fail with a validation error.

## Best Practices

1. **Validate data before batching**: Ensure all meeting URLs and configurations are valid
2. **Handle partial success**: Always check both `success` and `errors` arrays in the response
3. **Use appropriate batch sizes**: Consider processing in batches of 10-50 items for better error handling and easier debugging
4. **Monitor token balance**: Ensure you have sufficient tokens for all bots in the batch
5. **Check daily bot cap**: Make sure you won't exceed your daily bot creation limit

## Scheduled Bot Batch

You can also create multiple scheduled bots in a single request:

```bash
curl -X POST "https://api.meetingbaas.com/v2/bots/scheduled/batch" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '[
           {
             "meeting_url": "https://meet.google.com/abc-defg-hij",
             "bot_name": "Scheduled Bot 1",
             "join_at": "2025-01-20T14:00:00Z"
           },
           {
             "meeting_url": "https://zoom.us/j/123456789",
             "bot_name": "Scheduled Bot 2",
             "join_at": "2025-01-20T15:00:00Z"
           }
         ]'
```

## Token Reservation

Tokens are reserved individually for each bot in the batch. If one bot fails due to insufficient tokens, other bots in the batch may still succeed if tokens are available.

## Daily Bot Cap

The daily bot cap is checked for each bot individually. If you're creating 100 bots but your daily cap is 75, the first 75 will succeed and the remaining 25 will fail with `DAILY_BOT_CAP_REACHED`.



---

## Community & Support

Get help and connect with the Meeting BaaS community

### Source: ./content/docs/api-v2/community-and-support.mdx


Need help? We're here for you!

## Support Channels

- **Support Center**: Visit [Support center](https://dashboard.meetingbaas.com/support-center) to create and manage support tickets, view ticket status, and track your support requests
- **Discord**: Join our [Discord community](https://discord.com/invite/dsvFgDTr6c) for real-time support and discussions
- **Email**: Contact us at support@meetingbaas.com
- **Documentation**: Browse our comprehensive documentation

## Resources

- **API Reference**: Complete API documentation with examples
- **Getting Started Guides**: Step-by-step tutorials
- **Webhooks Guide**: Learn about webhook events and configuration
- **Examples**: Code samples in multiple languages

## Contributing

Found an issue or have a suggestion? We welcome contributions!

- Open an issue on GitHub
- Submit a pull request
- Share feedback in our Discord

## Status

Check our status page for real-time system status and incident updates.



---

## Deduplication & Rate Limiting

Learn about duplicate bot prevention and rate limiting in Meeting BaaS v2

### Source: ./content/docs/api-v2/deduplication-rate-limiting.mdx


Meeting BaaS v2 includes built-in protection against duplicate bots and rate limiting to ensure fair usage.

## Deduplication

Deduplication prevents multiple bots from joining the same meeting within a short time window.

### How It Works

By default, when you create a bot with `allow_multiple_bots: false`, the system:

1. Checks if a bot already exists for the same meeting URL within the last 5 minutes
2. If a bot exists, the request fails with `BOT_ALREADY_EXISTS`
3. If no bot exists, a lock is acquired and the bot is created

### Lock Duration

The deduplication lock lasts for **5 minutes**. After this time, you can create another bot for the same meeting URL.

### Allowing Multiple Bots

If you want to allow multiple bots in the same meeting, set `allow_multiple_bots: true` when creating the bot:

```json
{
  "meeting_url": "https://meet.google.com/abc-defg-hij",
  "bot_name": "Bot 1",
  "allow_multiple_bots": true
}
```

With `allow_multiple_bots: true`, no deduplication lock is applied, and multiple bots can join the same meeting.

### Use Cases

**Prevent duplicates (`allow_multiple_bots: false`):**
- Production environments where duplicate bots are unwanted
- Preventing accidental double-booking
- Ensuring only one bot per meeting

**Allow multiple bots (`allow_multiple_bots: true`):**
- Testing scenarios
- Multiple recording perspectives
- Backup bots for reliability

## Rate Limiting

Rate limiting controls how many requests per second your team can make to the API.

### How It Works

- Rate limits are applied **per team** (not per API key)
- Limits are measured in **requests per second**
- **GET requests are not rate limited** (list and get endpoints)
- Only POST, PATCH, and DELETE requests are rate limited

### Default Rate Limits

Default rate limits vary by plan:

- **Pay-as-you-go**: 5 requests/second
- **Pro**: 10 requests/second
- **Scale**: 20 requests/second
- **Enterprise**: 20 requests/second (can be customised)

### Rate Limit Headers

When making requests, the API includes rate limit headers:

- `x-ratelimit-limit`: Maximum requests per second
- `x-ratelimit-remaining`: Remaining requests in the current window
- `x-ratelimit-reset`: Time when the rate limit resets
- `retry-after`: Seconds to wait before retrying (when limit exceeded)

### Rate Limit Errors

When you exceed the rate limit, you'll receive a `429 Too Many Requests` response:

```json
{
  "success": false,
  "error": "Rate Limited",
  "code": "FST_ERR_TOO_MANY_REQUESTS",
  "statusCode": 429,
  "message": "Rate limit exceeded. Maximum <plan_limit> requests per second allowed. Retry after x seconds",
  "retryAfter": 1
}
```

The response includes a `retry-after` header indicating how many seconds to wait before retrying.

### Best Practices

1. **Respect rate limits**: Implement exponential backoff when you receive 429 errors
2. **Use batch operations**: Create multiple bots in a single request instead of multiple individual requests
3. **Cache responses**: Cache GET requests to reduce API calls
4. **Monitor headers**: Check rate limit headers to understand your current usage

## Daily Bot Cap

In addition to rate limiting, each team has a **daily bot creation limit**:

- **Pay-as-you-go**: 75 bots/day
- **Pro**: 300 bots/day
- **Scale**: 1,000 bots/day
- **Enterprise**: 3,000 bots/day

### How It Works

- The daily bot cap is checked **before** creating each bot
- The limit is based on a 24-hour rolling window
- If the limit is reached, subsequent bot creation requests fail with `DAILY_BOT_CAP_REACHED`
- The cap resets based on when bots were created (not a fixed time)

### Error Response

When the daily bot cap is reached:

```json
{
  "success": false,
  "error": "Rate Limited",
  "message": "Daily bot cap has been reached: 75 bots created within the last 24 hours",
  "code": "FST_ERR_DAILY_BOT_CAP_REACHED",
  "statusCode": 429
}
```

## Combining Limits

All limits work together:

1. **Rate limiting**: Controls requests per second
2. **Daily bot cap**: Controls total bots per day
3. **Token availability**: Controls whether you have tokens to create bots
4. **Deduplication**: Prevents duplicate bots (if enabled)

Make sure to account for all these limits when designing your integration.



---

## Error Codes

Complete reference for bot process error codes in Meeting BaaS v2

### Source: ./content/docs/api-v2/error-codes.mdx


When a bot fails, the error information is included in the `bot.failed` webhook event and in the bot details response. This page documents all possible error codes and their meanings.

## Error Code Structure

Error codes are standardized strings that indicate the reason a bot failed. They are included in:

- `bot.failed` webhook events (`error_code` field)
- Bot details response (`error_code` field)
- Bot status history

## Normal End Reasons

These codes indicate the bot ended normally (not a failure):

### `BOT_REMOVED`
**Title:** Bot Removed  
**Description:** Bot was removed from the meeting.

### `NO_ATTENDEES`
**Title:** No Attendees  
**Description:** No attendees joined the meeting.

### `NO_SPEAKER`
**Title:** No Speaker  
**Description:** No speakers detected during recording.

### `ALL_PARTICIPANTS_LEFT`
**Title:** All Participants Left  
**Description:** All human participants left the meeting. The bot leaves `timeout_config.everyone_left_timeout` seconds after the last participant (30 by default).

### `RECORDING_TIMEOUT`
**Title:** Recording Timeout  
**Description:** Recording timeout reached.

### `API_REQUEST`
**Title:** API Request  
**Description:** Recording stopped via API request (using the leave endpoint).

## Error End Reasons

These codes indicate the bot failed due to an error:

### `BOT_NOT_ACCEPTED`
**Title:** Bot Not Accepted  
**Description:** Bot was not accepted into the meeting, either by the participants or the meeting platform.

**Token Charging:** Only recording tokens are charged (based on waiting room duration).

### `TIMEOUT_WAITING_TO_START`
**Title:** Timeout Waiting to Start  
**Description:** Timeout waiting to start recording.

**Token Charging:** Only recording tokens are charged (based on waiting room duration).

### `CANNOT_JOIN_MEETING`
**Title:** Cannot Join Meeting  
**Description:** Cannot join meeting - meeting is not reachable or may not exist.

### `BOT_REMOVED_TOO_EARLY`
**Title:** Bot Removed Too Early  
**Description:** Bot was removed too early; the video is too short.

### `INVALID_MEETING_URL`
**Title:** Invalid Meeting URL  
**Description:** Invalid meeting URL provided.

### `STREAMING_SETUP_FAILED`
**Title:** Streaming Setup Failed  
**Description:** Failed to set up streaming audio.

### `LOGIN_REQUIRED`
**Title:** Login Required  
**Description:** Login required to access the meeting.

### `INTERNAL_ERROR`
**Title:** Internal Error  
**Description:** Internal error occurred during recording.

## Crash Reasons

These codes indicate the bot process crashed:

### `OOM_KILLED`
**Title:** Out of Memory  
**Description:** Bot process was killed due to out of memory.

### `SIGTERM`
**Title:** Process Terminated  
**Description:** Bot process was terminated.

### `FORCE_KILLED`
**Title:** Force Killed  
**Description:** Bot process was force killed.

### `GENERAL_ERROR`
**Title:** General Error  
**Description:** Bot process exited with a general error.

## Pre-Recording Stop

### `EXITING_MEETING_BEFORE_RECORD`
**Title:** Exiting Meeting Before Record
**Description:** The bot left the meeting before recording started. This can happen if the bot was requested to leave via the API (leave endpoint, scheduled bot deletion, or calendar bot cancellation), or if the meeting ended before the bot was accepted.

**Token Charging:** No tokens are consumed for pre-recording stops.

## Transcription Errors

### `TRANSCRIPTION_FAILED`
**Title:** Transcription Failed  
**Description:** The transcription process failed. Please try again using re-transcribe endpoint or contact support.

**Token Charging:** Recording and streaming tokens are charged, but transcription tokens are not.

## Zoom-Specific Errors

These errors are specific to Zoom meetings:

### `WAITING_FOR_HOST_TIMEOUT`
**Title:** Waiting for Host Timeout
**Description:** The bot timed out while waiting for the meeting host to join the meeting.

**Resolution:** Ensure the meeting host joins before the timeout expires. You can increase the timeout via `timeout_config.waiting_room_timeout`.

### `WAITING_FOR_AUTHORIZED_USER_TIMEOUT`
**Title:** Waiting for Authorized User Timeout
**Description:** The bot timed out waiting for the authorized user (associated with the OBF token) to join the meeting. When using OBF tokens, the Zoom user who authorized your app must be present in the meeting for the bot to join successfully. The bot retries joining every few seconds, but if the authorized user never appears, this timeout is triggered.

**Resolution:** Ensure the authorized user joins the meeting before or shortly after the bot. You can increase the timeout via `timeout_config.waiting_room_timeout`.

### `UNABLE_JOIN_EXTERNAL_MEETING`
**Title:** Unable to Join External Meeting
**Description:** The Zoom SDK app is not authorized to join meetings hosted by a different Zoom organization. This occurs when the SDK app's configuration restricts it to meetings within its own Zoom organization.

**Resolution:** Ensure the Zoom SDK app has permission to join external meetings in its [Zoom Marketplace app settings](https://marketplace.zoom.us/). Alternatively, use an OBF token from a user within the meeting's Zoom organization.

### `RECORDING_RIGHTS_NOT_GRANTED`
**Title:** Recording Rights Not Granted  
**Description:** The bot was unable to obtain recording rights from the meeting host.

### `CANNOT_REQUEST_RECORDING_RIGHT`
**Title:** Cannot Request Recording Right  
**Description:** The bot could not request recording rights. The meeting may not have recording enabled.

### `MEETING_ENDED_PREMATURELY`
**Title:** Meeting Ended Prematurely  
**Description:** The meeting ended before the bot could participate.

### `SET_ZOOM_ID_AND_PWD_TOGETHER`
**Title:** Zoom SDK Configuration Error  
**Description:** Zoom SDK ID and password must be set together.

### `CANNOT_GET_JWT_TOKEN`
**Title:** Cannot Get JWT Token  
**Description:** Unable to obtain JWT token with the provided Zoom SDK credentials.

### `SDK_AUTH_FAILED`
**Title:** SDK Authentication Failed  
**Description:** Zoom SDK authentication failed with the provided credentials.

### `ZOOM_ACCESS_TOKEN_ERROR`
**Title:** Zoom Access Token Error
**Description:** An error occurred while obtaining the Zoom access token (ZAK token). This can happen when using `zak_token_url` and the endpoint fails to return a valid token.

### `ZOOM_OBF_TOKEN_ERROR`
**Title:** Zoom OBF Token Error
**Description:** An error occurred while obtaining or using the OBF (On Behalf Of) token. This can happen when:

- The `obf_token` provided is invalid or expired
- The `obf_token_url` endpoint fails to return a valid token
- The stored credential (`credential_id`) has invalid or expired OAuth tokens
- Token refresh fails for managed OAuth credentials

**Resolution:** Check your OBF token configuration. If using stored credentials, verify the credential state is "active" via `GET /v2/zoom-credentials/{id}`. If the credential is invalid, prompt the user to re-authorize.

### `RECORDING_START_TIMEOUT`
**Title:** Recording Start Timeout
**Description:** Recording privilege was granted by the host, but the recording never started within the expected time. This may indicate an issue with the meeting platform's recording system.

**Token Charging:** Recording tokens are charged based on the time spent waiting.

### `HOST_CLIENT_CANNOT_GRANT_PERMISSION`
**Title:** Host Client Cannot Grant Permission
**Description:** The meeting host is using a Zoom client (such as Zoom Rooms) that cannot display the recording permission dialog. The bot cannot record this meeting.

**Resolution:** This is a limitation of certain Zoom clients. The host would need to join from a standard Zoom desktop or mobile client to grant recording permission.

## Google Meet Authentication Errors

These errors apply to [authenticated Google Meet bots](/docs/api-v2/authenticated-bots/meet) that sign in as a Google Workspace user via SAML SSO (`meet_config`).

### `MEET_LOGIN_UNAVAILABLE`
**Title:** Meet Login Unavailable
**Description:** No meet login slot was available to authenticate the bot — every matching login was saturated (at its concurrent-session capacity) or no active login matched the selector — and `meet_config.fallback` was `fail`.

**Resolution:** Add more logins to the pool, reduce concurrency, or set `meet_config.fallback` to `anonymous`. Monitor headroom with `GET /v2/meet-logins/utilization` and the **Meet Login Utilization** alert.

### `MEET_LOGIN_REQUIRED`
**Title:** Meet Login Required
**Description:** The meeting required a signed-in user, but the bot could not authenticate (no `meet_config` was supplied, or the selected login could not be used).

**Resolution:** Send the bot with a valid `meet_config` and ensure the selected login's state is `active` via `GET /v2/meet-logins/{credential_id}`.

### `MEET_LOGIN_FAILED_SAML_REJECTED`
**Title:** Meet Login Failed — SAML Rejected
**Description:** Google rejected the SAML assertion during sign-in. Usually the certificate uploaded to Google Admin Console no longer matches the workspace's certificate, or the Legacy SSO profile is misconfigured or unassigned.

**Resolution:** Verify the certificate in Google Admin matches the workspace `cert_pem` and that the SSO profile points at the `/v2/meet-sso/*` endpoints and is assigned to the bot group/OU that contains the account. The workspace auto-flips to `invalid`; re-enable it via `PATCH /v2/meet-workspaces/{workspace_id}` after fixing the configuration.

### `MEET_LOGIN_FAILED_TIMEOUT`
**Title:** Meet Login Failed — Timeout
**Description:** The SSO sign-in flow did not complete within the expected time.

**Resolution:** Confirm the Workspace user completed its first-time interactive "Welcome to Workspace" login — this must be done in a browser after the account is created and **before** the Legacy SSO profile is assigned to it — and that the account is not suspended, then retry. The login may auto-flip to `invalid`; re-enable it via `PATCH /v2/meet-logins/{credential_id}` after resolving the cause.

## System Errors

These errors occur when the system attempts to create a bot instance. For immediate bots, this happens at creation time and the error is returned in the API response. For scheduled and calendar bots, these errors can appear in `bot.failed` webhook events when the bot is being queued to join the meeting (at its scheduled join time).

### `INSUFFICIENT_TOKENS`
**Title:** Insufficient Tokens  
**Description:** Not enough tokens were available to launch the bot.

**When it occurs:**
- **Immediate bots**: When you call `POST /v2/bots` (error returned in API response)
- **Scheduled bots**: When the bot is being queued at its `join_at` time (error sent via `bot.failed` webhook)
- **Calendar bots**: When the bot is being queued at its scheduled join time (error sent via `bot.failed` webhook)

### `DAILY_BOT_CAP_REACHED`
**Title:** Daily Bot Cap Reached  
**Description:** The daily bot creation limit configured for this team has been reached.

**When it occurs:**
- **Immediate bots**: When you call `POST /v2/bots` (error returned in API response)
- **Scheduled bots**: When the bot is being queued at its `join_at` time (error sent via `bot.failed` webhook)
- **Calendar bots**: When the bot is being queued at its scheduled join time (error sent via `bot.failed` webhook)

**Note:** For scheduled and calendar bots, the daily bot cap is checked when the bot is being queued, not when it's scheduled. This means a bot scheduled for later in the day might fail if the daily cap is reached before its scheduled time.

### `BOT_ALREADY_EXISTS`
**Title:** Bot Already Exists  
**Description:** A bot is already running for this meeting URL.

**When it occurs:**
- **Immediate bots**: When you call `POST /v2/bots` and `allow_multiple_bots` is `false` (error returned in API response)
- **Scheduled bots**: When the bot is being queued and another bot already exists for the same meeting URL (error sent via `bot.failed` webhook)
- **Calendar bots**: When the bot is being queued and another bot already exists for the same meeting URL (error sent via `bot.failed` webhook)

## Unknown Error

### `UNKNOWN_ERROR`
**Title:** Unknown Error  
**Description:** An unknown error occurred. Please contact support.

This is a fallback error code used when the actual error cannot be determined or mapped to a known error code.

## Token Charging

Different error codes result in different token charges:

- **User-responsible errors** (`BOT_NOT_ACCEPTED`, `TIMEOUT_WAITING_TO_START`): Only recording tokens charged (based on waiting room duration)
- **Transcription errors** (`TRANSCRIPTION_FAILED`): Recording and streaming tokens charged, transcription tokens not charged
- **Other errors**: No tokens charged (reserved tokens are released)
- **Normal end reasons**: Full tokens charged based on meeting duration and features used

## Handling Errors

When you receive a `bot.failed` webhook:

1. Check the `error_code` to understand what went wrong
2. Review the `error_message` for additional context
3. For user-responsible errors (`BOT_NOT_ACCEPTED`, `TIMEOUT_WAITING_TO_START`, `WAITING_FOR_AUTHORIZED_USER_TIMEOUT`), ensure meeting settings allow bots and authorized users join promptly
4. For transcription errors, you can retry transcription using the re-transcribe endpoint
5. For system errors, check your token balance and daily bot cap
6. For unknown errors, contact support with the bot ID and error details



---

## Introduction

Get started with the Meeting BaaS API v2

### Source: ./content/docs/api-v2/index.mdx


<Callout type="info">
  Meeting BaaS API v2 is the latest version of our API, featuring improved architecture, better error handling, and enhanced features. 
  Both v1 and v2 APIs are currently available and will run in parallel. For v1 documentation, see [API v1](/docs/api).
</Callout>

**Meeting BaaS** 🐟 provides _Meetings Bots As A Service_, with integrated transcription.

This allows you to:

1. **interact with**
2. **transcribe**
3. **AI summarize**

video-meetings through a single unified API. Using Meeting BaaS, you can deploy bots on Microsoft Teams, Google Meet, and Zoom in less than 1 minute.

Our meeting bots act as regular meeting participants with full audio and visual capabilities.

They can listen, speak, use chat, and appear with customizable names and profile pictures.

Just provide a meeting URL through a simple command, and meeting bots will connect to the meeting, give their name and ask to be let in.

Once inside, they record the meeting until it ends, and provide you with the data as they go.

## What's New in v2?

- **Enhanced Error Handling**: More detailed error messages and standardized error responses
- **Better Token Management**: Improved token reservation and consumption tracking
- **Batch Operations**: Create multiple bots in a single request with partial success support
- **Advanced Filtering**: More powerful query parameters for listing bots and events
- **Comprehensive Webhooks**: Detailed webhook events for bot and calendar operations
- **Calendar Integration**: Enhanced calendar sync and bot scheduling features
- **Rate Limiting**: Liberal rate limits per team
- **Deduplication**: Built-in protection against duplicate bot creation

## Getting Started

Ready to start? Check out our getting started guides to learn how to:

- [Send your first bot to a meeting](/docs/api-v2/getting-started/sending-a-bot)
- [Retrieve meeting data](/docs/api-v2/getting-started/getting-the-data)
- [Set up webhooks](/docs/api-v2/getting-started/webhooks)
- [Integrate calendars](/docs/api-v2/getting-started/calendars)

## API Reference

Browse the complete [API Reference](/docs/api-v2/reference) for detailed documentation on all endpoints, request/response schemas, and error codes.



---

## Migration Guide

Complete guide to migrating from Meeting BaaS API v1 to v2

### Source: ./content/docs/api-v2/migration-guide.mdx


This guide helps you migrate your integration from Meeting BaaS API v1 to v2. Both APIs will run in parallel, but v2 offers improved architecture, better error handling, and enhanced features.

<Callout type="warn">
  **Important**: Meeting BaaS v2 does **not** support automatic data migration. Bot data, calendar connections, and scheduled bots from v1 will not be automatically migrated to v2. You'll need to recreate calendar connections and any scheduled bots in v2.
</Callout>

## Overview of Changes

### Key Architectural Changes

- **API Versioning**: v2 uses `/v2/*` prefix for all endpoints
- **Response Format**: Standardized `{success, data, error}` structure
- **Error Handling**: Consistent error codes with `FST_ERR_` prefix

### What's New in v2

- **Batch Operations**: Create multiple bots in a single request
- **Scheduled Bots**: Separate endpoints for scheduled bots with update/delete support
- **Enhanced Webhooks**: More detailed webhook events and callbacks
- **Better Error Codes**: Standardized error codes for programmatic handling
- **Rate Limiting**: Per-team rate limits with clear error messages
- **Deduplication**: Built-in protection against duplicate bot creation
- **Calendar Integration**: Improved calendar sync and bot scheduling

## Authentication Changes

### v1 Authentication

v1 public API routes used API key authentication:

- **Header**: `x-meeting-baas-api-key` or `x-spoke-api-key` (legacy)
- **Required**: All public `/bots/*` and `/calendars/*` endpoints required this header

### v2 Authentication

v2 uses the same API key authentication:

- **Header**: `x-meeting-baas-api-key`
- **Required**: All `/v2/*` endpoints require this header
- **Legacy Header**: `x-spoke-api-key` is no longer supported

**Migration Step**: 
- Ensure you're using `x-meeting-baas-api-key` header (not the legacy `x-spoke-api-key`)
- No other authentication changes needed - API key authentication works the same way

## Endpoint Mapping

### Bot Endpoints

| v1 Endpoint | v2 Endpoint | Changes |
|------------|-------------|---------|
| `POST /bots` | `POST /v2/bots` | Response format changed |
| `GET /bots/bots_with_metadata` | `GET /v2/bots` | New filtering options |
| `GET /bots/meeting_data` | `GET /v2/bots/:bot_id` | Different response structure |
| `DELETE /bots/:uuid` | `POST /v2/bots/:bot_id/leave` | Method changed, new status requirements |
| `POST /bots/:uuid/delete_data` | `DELETE /v2/bots/:bot_id/delete-data` | Method changed, path updated |
| `GET /bots/:uuid/screenshots` | `GET /v2/bots/:bot_id/screenshots` | Path parameter name changed |
| - | `GET /v2/bots/:bot_id/status` | New endpoint for lightweight status checks |
| - | `POST /v2/bots/batch` | New batch creation endpoint |
| - | `POST /v2/bots/scheduled` | New scheduled bot creation |
| - | `GET /v2/bots/scheduled` | New scheduled bot listing |
| - | `GET /v2/bots/scheduled/:bot_id` | New scheduled bot details |
| - | `PATCH /v2/bots/scheduled/:bot_id` | New scheduled bot update |
| - | `DELETE /v2/bots/scheduled/:bot_id` | New scheduled bot deletion |

### Calendar Endpoints

| v1 Endpoint | v2 Endpoint | Changes |
|------------|-------------|---------|
| `POST /calendars` | `POST /v2/calendars` | OAuth credentials required in request |
| `GET /calendars` | `GET /v2/calendars` | Response format changed |
| `GET /calendar_events` | `GET /v2/calendars/:calendar_id/events` | Path structure changed |
| - | `POST /v2/calendars/list-raw` | New endpoint to preview calendars |
| - | `GET /v2/calendars/:calendar_id` | New endpoint for calendar details |
| - | `PATCH /v2/calendars/:calendar_id` | New endpoint to update credentials |
| - | `DELETE /v2/calendars/:calendar_id` | New endpoint to delete connection |
| - | `POST /v2/calendars/:calendar_id/sync` | New endpoint to force sync |
| - | `POST /v2/calendars/:calendar_id/bots` | New endpoint to schedule bots for events |

## Request/Response Format Changes

### Response Structure

**v1 Response** (varied by endpoint):
```json
{
  "bot_id": "uuid",
  "status": "in_call_recording",
  ...
}
```

**v2 Response** (standardized):
```json
{
  "success": true,
  "data": {
    "bot_id": "uuid",
    "status": "in_call_recording",
    ...
  }
}
```

**Error Response** (v2):
```json
{
  "success": false,
  "error": "status code label",
  "message": "Human-readable error message",
  "code": "FST_ERR_BOT_NOT_FOUND_BY_ID",
  "statusCode": 404,
  "details": null
}
```

### Field Naming

Field naming conventions remain the same between v1 and v2:

- `bot_id`
- `created_at`
- `meeting_url`
- `bot_name`

**No changes needed** - field naming remains consistent between v1 and v2.

### Bot Creation Request

**v1**:
```json
{
  "meeting_url": "https://meet.google.com/...",
  "bot_name": "AI Notetaker",
  "recording_mode": "speaker_view",
  "start_time": "2025-01-20T14:00:00Z"  // Optional, for scheduling
}
```

**v2** (Immediate):
```json
{
  "meeting_url": "https://meet.google.com/...",
  "bot_name": "AI Notetaker",
  "recording_mode": "speaker_view",
  "transcription_enabled": true,
  "transcription_config": {
    "provider": "gladia"
  }
}
```

**v2** (Scheduled - separate endpoint):
```json
{
  "meeting_url": "https://meet.google.com/...",
  "bot_name": "AI Notetaker",
  "recording_mode": "speaker_view",
  "join_at": "2025-01-20T14:00:00Z"  // Required for scheduled bots
}
```

**Key Changes**:
- Scheduled bots use separate endpoint (`POST /v2/bots/scheduled`)
- `start_time` renamed to `join_at` for scheduled bots
- `transcription_enabled` and `transcription_config` are explicit in v2

## Error Handling Changes

### Error Codes

v2 uses standardized error codes with `FST_ERR_` prefix:

| v1 Error | v2 Error Code | Description |
|----------|---------------|-------------|
| `InvalidApiKey` | `FST_ERR_FORBIDDEN` | Invalid or missing API key |
| `BotNotFound` | `FST_ERR_BOT_NOT_FOUND_BY_ID` | Bot not found |
| `InsufficientTokens` | `FST_ERR_INSUFFICIENT_TOKENS` | Not enough tokens |
| `TooManyRequests` | `FST_ERR_TOO_MANY_REQUESTS` | Rate limit exceeded |
| - | `FST_ERR_BOT_ALREADY_EXISTS` | Duplicate bot detected |
| - | `FST_ERR_DAILY_BOT_CAP_REACHED` | Daily bot limit reached |
| - | `FST_ERR_BOT_STATUS` | Invalid bot status for operation |

### Error Response Format

**v1** (varied):
```json
{
  "error": "Bot not found"
}
```

**v2** (standardized):
```json
{
  "success": false,
  "error": "Not Found",
  "message": "Bot with ID 'uuid' not found",
  "code": "FST_ERR_BOT_NOT_FOUND_BY_ID",
  "statusCode": 404,
  "details": null
}
```

**Migration Step**: Update error handling code to check `success: false` and use `code` field for programmatic error handling.

## Webhook Changes

### Webhook Events

v2 introduces new webhook event types and improved payloads:

**New Events in v2**:
- `bot.status_change` - Bot status transitions
- `calendar.connection_created` - Calendar connection established
- `calendar.connection_updated` - Calendar credentials updated
- `calendar.connection_deleted` - Calendar connection removed
- `calendar.connection_error` - Calendar sync errors
- `calendar.events_synced` - Calendar events synced
- `calendar.event_created` - New calendar event detected
- `calendar.event_updated` - Calendar event modified
- `calendar.event_cancelled` - Calendar event cancelled

**Enhanced Events**:
- `bot.completed` - More detailed payload with error information
- `bot.failed` - Standardized error codes and messages

### Webhook Payload Structure

**v1** (varied by event):
```json
{
  "event": "bot.completed",
  "bot_id": "uuid",
  "status": "completed",
  ...
}
```

**v2** (standardized):
```json
{
  "event": "bot.completed",
  "data": {
    "bot_id": "uuid",
    "status": "completed",
    "error_code": null,
    "error_message": null,
    ...
  },
  "sent_at": "2025-01-20T14:00:00Z"
}
```

### Callbacks

v2 introduces **callbacks** - bot-specific HTTP POST/PUT requests separate from account-level webhooks:

- Configured per bot via `callback_config`
- Only sent for `bot.completed` and `bot.failed` events
- Uses the same payload structure as webhooks

**Migration Step**: Update webhook handlers to:
1. Check `event` field (same as v1, but new event types added)
2. Access data via `data` object
3. Handle new calendar webhook events
4. Consider implementing callbacks for bot-specific notifications

## Transcription Changes

v2 introduces significant improvements to transcription handling, providing better security, flexibility, and access to raw transcription data.

### Storage and Access Model

**v1 Transcription**:
- Transcription data embedded in webhook payloads
- No access to raw provider responses
- No transcription ID tracking

**v2 Transcription**:
- **S3-based storage**: All transcriptions stored as JSON files in S3
- **Presigned URLs**: Access transcriptions via secure, time-limited presigned URLs
- **Raw transcription access**: Full provider response preserved (includes LLM summaries if configured)
- **Transcription ID tracking**: Each transcription has a unique `provider_id` (useful for BYOK users)
- **Standardized output**: Consistent `output_transcription.json` format regardless of provider

### Transcription Files

v2 creates multiple transcription artifacts:

1. **Raw Transcription** (`raw_transcription.json`):
   - Contains the complete, unmodified response from the transcription provider
   - Includes all custom parameters you configured (e.g., LLM summaries, language detection)
   - Preserves provider-specific metadata
   - Multiple transcription chunks are combined into a single file
   - Accessible via presigned URL in bot artifacts
   - **Note**: Raw transcriptions are presented as an array without time duration offsets or speaker diarization. They're best used alongside the final `output_transcription.json` which includes proper timestamp adjustments and speaker mappings.

2. **Output Transcription** (`output_transcription.json`):
   - Standardized format across all providers
   - Diarized with speaker names mapped from meeting participants
   - Timestamps adjusted for multi-chunk transcriptions
   - Accessible via presigned URL in bot artifacts

### Accessing Transcriptions

**v1** (embedded in webhook):
```json
{
  "event": "complete",
  "bot_id": "uuid",
  "transcript": [
    {
      "speaker": "John Doe",
      "text": "Hello everyone",
      "start_time": 0.5,
      "end_time": 2.1
    }
  ]
}
```

**v2** (via presigned URLs):
```json
{
  "event": "bot.completed",
  "data": {
    "bot_id": "uuid",
    "raw_transcription": "https://s3.amazonaws.com/...",
    "transcription": "https://s3.amazonaws.com/...",
    "transcription_ids": ["gladia-job-12345"],
    "transcription_provider": "gladia"
  }
}
```

### Transcription IDs for BYOK Users

v2 provides `transcription_ids` as an array of provider job IDs in bot details and webhook payloads:

- **Useful for BYOK (Bring Your Own Key)**: Track your own transcription jobs with the provider
- **Error correlation**: Match transcription errors to specific provider job IDs
- **Multi-chunk support**: Each audio chunk gets its own provider ID

**Example** (from bot details or webhook):
```json
{
  "transcription_ids": ["gladia-job-12345", "gladia-job-12346"],
  "transcription_provider": "gladia"
}
```

### Custom Parameters and LLM Summaries

v2 preserves all custom parameters you pass to the transcription provider:

**Request**:
```json
{
  "transcription_config": {
    "provider": "gladia",
    "custom_parameters": {
      "llm_summary": true,
      "summary_prompt": "Summarize this meeting",
      "language_detection": true
    }
  }
}
```

**Raw Transcription Response** (in `raw_transcription.json`):
```json
{
  "bot_id": "uuid",
  "transcriptions": [
    {
      "transcription": {
        "utterances": [...],
        "summary": "Meeting discussed Q4 goals...",  // LLM summary if configured
        "languages": ["en", "es"],
        "metadata": {...}
      }
    }
  ]
}
```

### Security Improvements

**v1**: Transcription data sent directly in webhook payloads (potential size limits, security concerns)

**v2**: 
- Transcriptions stored securely in S3
- Access via presigned URLs with expiration
- No sensitive data in webhook payloads
- Better handling of large transcriptions
- Supports multi-chunk recordings without payload size issues

### Migration Steps

1. **Update Webhook Handlers**:
   - Instead of reading transcript from webhook payload, fetch from presigned URL
   - Handle both `raw_transcription` and `transcription` artifacts
   - Download and parse JSON files from S3

2. **Handle Presigned URLs**:
   - Presigned URLs expire after a set time (typically 24 hours)
   - Download transcriptions promptly after receiving webhook
   - Store transcriptions in your own storage if needed long-term

3. **Use Transcription IDs** (if BYOK):
   - Access `transcription_ids` array from bot details or webhook payload
   - Correlate with your own provider job tracking
   - Use for error handling and debugging

4. **Leverage Raw Transcription**:
   - Access LLM summaries and custom provider features
   - Use raw data for custom processing
   - Preserve provider-specific metadata

**Example Migration**:

**v1 Code**:
```javascript
webhookHandler(event) {
  const transcript = event.transcript; // Direct access
  processTranscript(transcript);
}
```

**v2 Code**:
```javascript
webhookHandler(event) {
  // Get transcription URL from webhook payload
  const transcriptionUrl = event.data.transcription;
  
  if (transcriptionUrl) {
    // Download from presigned URL
    const response = await fetch(transcriptionUrl);
    const transcription = await response.json();
    
    processTranscript(transcription.result.utterances);
  }
}
```

## Zoom Credentials and AAN Attribution

v2 introduces the [Credentials API](/docs/api-v2/authenticated-bots/zoom/credentials) for secure storage of Zoom SDK credentials and OAuth tokens. This replaces the v1 pattern of passing credentials with every bot request.

### Why This Matters: Active Apps Notifier (AAN)

Zoom's [Active Apps Notifier](https://developers.zoom.us/docs/meeting-sdk/ui-notices/#active-apps-notifier-aan-use-case) displays which app is accessing meeting content. The app name shown is determined by the SDK credentials used to initialize the session. During Marketplace review, Zoom requires the AAN to display **your** app name — not "Meeting Baas".

### v1: SDK Credentials Per Request

In v1, you pass your SDK credentials with every bot request:

```json
{
  "meeting_url": "https://zoom.us/j/123456789",
  "bot_name": "Recording Bot",
  "zoom_sdk_id": "YOUR_SDK_KEY",
  "zoom_sdk_pwd": "YOUR_SDK_SECRET",
  "zoom_obf_token_url": "https://your-api.com/zoom/obf-token"
}
```

This works but requires sending secrets with every API call.

### v2: Store Once, Reference by ID

In v2, store your credentials once and reference them by ID:

**Step 1: Create a credential** (one-time setup via API or [Dashboard](https://app.meetingbaas.com)):

```bash
curl -X POST "https://api.meetingbaas.com/v2/zoom-credentials" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "name": "Production Zoom App",
           "client_id": "YOUR_ZOOM_CLIENT_ID",
           "client_secret": "YOUR_ZOOM_CLIENT_SECRET"
         }'
```

**Step 2: Reference in bot requests:**

```json
{
  "bot_name": "Recording Bot",
  "meeting_url": "https://zoom.us/j/123456789",
  "zoom_config": {
    "credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "obf_token_url": "https://your-api.com/zoom/obf-token"
  }
}
```

### Field Mapping

| v1 Field | v2 Equivalent | Notes |
|----------|---------------|-------|
| `zoom_sdk_id` | Stored in credential | No longer passed per-request |
| `zoom_sdk_pwd` | Stored in credential | No longer passed per-request |
| `zoom_obf_token` | `zoom_config.obf_token` | Moved into `zoom_config` object |
| `zoom_obf_token_url` | `zoom_config.obf_token_url` | Moved into `zoom_config` object |
| `zoom_obf_token_user_id` | `zoom_config.credential_id` or `zoom_config.credential_user_id` | v2 uses unified Credentials API instead of separate OAuth connections |
| `zoom_access_token_url` | `zoom_config.zak_token_url` | Renamed, moved into `zoom_config` |

### Migration Steps

1. **Create credentials in v2**: Store your Zoom app's SDK credentials ([how to find them](https://developers.zoom.us/docs/meeting-sdk/get-credentials/#get-meeting-sdk-credentials)) via `POST /v2/zoom-credentials` or the Dashboard
2. **Update bot requests**: Replace `zoom_sdk_id`/`zoom_sdk_pwd` with `zoom_config.credential_id`
3. **Move OBF config**: Move `zoom_obf_token`/`zoom_obf_token_url` into the `zoom_config` object
4. **Migrate OAuth connections**: If using v1's `zoom_obf_token_user_id`, create user-type credentials in v2 and switch to `zoom_config.credential_id`

<Callout>
v2 credentials are encrypted at rest with AES-256-GCM. Secrets are never returned in API responses. This is more secure than passing credentials with every request in v1.
</Callout>

## Calendar Integration Changes

### OAuth Model

Both v1 and v2 use a **bring-your-own-credentials** model where you create and manage your own OAuth applications.

**Key Difference in v2**:
- v2 requires you to provide OAuth credentials (`oauth_client_id`, `oauth_client_secret`, `oauth_refresh_token`) when creating calendar connections
- v1 only required the `refresh_token` (client credentials were managed separately)
- v2 gives you more control by requiring explicit credential management

### Calendar Connection Creation

**v1**:
```json
POST /calendars
{
  "platform": "google",
  "refresh_token": "user_refresh_token"
}
```

**v2**:
```json
POST /v2/calendars
{
  "calendar_platform": "google",
  "oauth_client_id": "your_client_id",
  "oauth_client_secret": "your_client_secret",
  "oauth_refresh_token": "user_refresh_token",
  "raw_calendar_id": "primary"
}
```

**Key Changes**:
- Must provide your own OAuth `client_id` and `client_secret`
- Must specify `raw_calendar_id` (use `POST /v2/calendars/list-raw` to get available calendars)
- Microsoft requires `oauth_tenant_id` (defaults to `"common"`)

**Migration Step**: 
1. Create OAuth applications with Google/Microsoft (see [Calendar Integration Guide](/docs/api-v2/getting-started/calendars))
2. Implement OAuth flow to get user refresh tokens
3. Recreate all calendar connections in v2 with new credentials

## Token Import from v1

v2 allows you to import your remaining tokens from v1, ensuring a smooth transition without losing your token balance.

### How to Import Tokens

1. **Access Token Settings**: Navigate to Settings > Usage in the v2 dashboard
2. **Click "Import from v1"**: This opens the import dialog
3. **Check Available Tokens**: The dialog shows your available v1 token balance
4. **Enter Amount**: Specify how many tokens to import (can import all or a portion)
5. **Confirm Import**: Tokens are immediately added to your v2 team's balance

### Important Notes

- **UI Only**: Token import is only available via the dashboard UI, not through the API
- **Flexible Import**: You can import tokens multiple times at your own pace - import all at once or in smaller batches as needed
- **Team-Based**: Imported tokens go to your current team's balance
- **Irreversible**: Once imported, tokens cannot be transferred back to v1
- **No Expiration**: Imported tokens don't expire and work the same as purchased tokens

## Data Migration

<Callout type="warn">
  **No Automatic Data Migration**: Meeting BaaS v2 does **not** automatically migrate data from v1. You'll need to:
</Callout>

### What's NOT Migrated

- **Bot Data**: Historical bot recordings, transcriptions, and metadata
- **Calendar Connections**: All calendar connections must be recreated
- **Scheduled Bots**: All scheduled bots must be recreated
- **Webhook Configurations**: Webhook endpoints must be reconfigured

### What You Need to Do

1. **Import Tokens** (if applicable):
   - Import remaining tokens from v1 (see [Token Import](#token-import-from-v1) above)
   - This ensures you don't lose your token balance

2. **Export Important Data** (if needed):
   - Download any bot recordings or transcriptions you need to keep
   - Note any scheduled bot configurations
   - Document calendar connection mappings

3. **Recreate Calendar Connections**:
   - Set up OAuth applications (see [Calendar Integration Guide](/docs/api-v2/getting-started/calendars))
   - Reconnect all user calendars via v2 API
   - Reschedule any calendar-based bots

4. **Recreate Scheduled Bots**:
   - List all active scheduled bots in v1
   - Recreate them in v2 using `POST /v2/bots/scheduled`

5. **Update Webhook Endpoints**:
   - Configure webhook endpoints in v2
   - Update webhook handlers to support new event types

## Step-by-Step Migration Checklist

### Phase 1: Preparation

- [ ] Review v2 API documentation
- [ ] Set up OAuth applications for calendar integration (if using calendars)
- [ ] Export any critical data from v1
- [ ] Test v2 API with a new API key in a development environment

### Phase 2: Code Updates

- [ ] Update base URL from `/bots` to `/v2/bots`
- [ ] Update authentication to use only `x-meeting-baas-api-key` header
- [ ] Update request/response parsing for standardized format
- [ ] Update error handling for new error codes
- [ ] Separate immediate and scheduled bot creation logic
- [ ] Update webhook handlers for new event structure
- [ ] Implement calendar OAuth flow (if using calendars)
- [ ] Store Zoom SDK credentials via `/v2/zoom-credentials` or Dashboard (if using Zoom)
- [ ] Replace `zoom_sdk_id`/`zoom_sdk_pwd` with `zoom_config.credential_id`
- [ ] Move OBF token config into `zoom_config` object

### Phase 3: Testing

- [ ] Test bot creation (immediate)
- [ ] Test bot creation (scheduled)
- [ ] Test bot listing and filtering
- [ ] Test bot status checks
- [ ] Test bot leave operation
- [ ] Test bot data deletion
- [ ] Test webhook delivery
- [ ] Test calendar integration (if using)
- [ ] Test error scenarios

### Phase 4: Deployment

- [ ] Deploy updated code to staging
- [ ] Monitor webhook delivery
- [ ] Verify calendar sync (if using)
- [ ] Deploy to production
- [ ] Monitor for issues

### Phase 5: Data Migration

- [ ] Recreate calendar connections in v2
- [ ] Reschedule any calendar-based bots
- [ ] Recreate scheduled bots in v2
- [ ] Configure webhook endpoints in v2
- [ ] Verify all integrations are working

## Common Migration Issues

### Issue: "Invalid API Key"

**Solution**: Ensure you're using the `x-meeting-baas-api-key` header (not JWT or legacy header).

### Issue: "Field not found" errors

**Solution**: Verify that you're using the correct field names from the API response. Field naming conventions are consistent between v1 and v2.

### Issue: Webhooks not received

**Solution**: 
1. Verify webhook endpoint is configured in v2
2. Check webhook payload structure matches v2 format
3. Ensure your endpoint can handle new event types

### Issue: Calendar connection fails

**Solution**:
1. Verify OAuth credentials are correct
2. Ensure refresh token includes `offline_access` scope (Microsoft) or `access_type=offline` (Google)
3. Check that Google Calendar API is enabled in your Google Cloud project

### Issue: Scheduled bots not executing

**Solution**:
1. Verify `join_at` is in the future
2. Check bot status via `GET /v2/bots/scheduled/:bot_id`
3. Ensure sufficient tokens are available at execution time

## Getting Help

If you encounter issues during migration:

1. **Documentation**: Check the [v2 API Reference](/docs/api-v2/reference)
2. **Error Codes**: See [Error Codes Guide](/docs/api-v2/error-codes)
3. **Support**: Visit [Support Center](https://dashboard.meetingbaas.com/support-center)
4. **Community**: Join our Discord server

## Next Steps

After completing migration:

1. **Monitor**: Keep an eye on webhook delivery and error rates
2. **Optimize**: Take advantage of v2 features like batch operations
3. **Update**: Keep your integration updated with new v2 features

---

<Callout type="info">
  Both v1 and v2 APIs will continue to run in parallel. You can migrate at your own pace, but we recommend migrating to v2 to take advantage of improved features and better error handling.
</Callout>



---

## New Features

Discover all the enhancements and improvements in Meeting BaaS API v2

### Source: ./content/docs/api-v2/new-features.mdx


Meeting BaaS v2 introduces significant improvements across security, transparency, developer experience, and feature availability. This document highlights the key enhancements that make v2 a compelling upgrade from v1.

## Microsoft Teams Authenticated Bots

v2 lets bots join Microsoft Teams as **authenticated Microsoft 365 users** with stored credentials, instead of only as anonymous guests.

**v1**: Anonymous Teams joins only — bots failed on meetings restricted to signed-in users and waited in the lobby as guests.

**v2**:
- **Authenticated joins**: Bots sign in as a real Microsoft 365 user (email + password) from a tenant you control, so they can join meetings restricted to signed-in / in-organization users and get admitted past the lobby.
- **No SAML, no certificates**: Teams sign-in is credential-based — there's no identity provider, keypair, or SSO profile to configure. The one requirement is an MFA-free account.
- **Teams Workspaces & Teams Logins**: New `/v2/teams-workspaces` and `/v2/teams-logins` resources group your Microsoft 365 tenant and the accounts bots sign in as. Passwords are encrypted at rest with AES-256-GCM and are never returned.
- **Round-robin pools**: Group logins by `email_group` and the dispatcher assigns the least-loaded active login (up to 20 concurrent sessions each by default). Add logins to scale capacity linearly.
- **Configurable fallback**: Per bot, choose to `fail` (default) or fall back to an `anonymous` join when the pool is saturated.
- **Utilization**: `GET /v2/teams-logins/utilization` reports live pool concurrency so you can stay ahead of saturation.

### Benefits

- Record Teams meetings that block anonymous guests
- Get admitted past the lobby as an organization member
- Scale authenticated capacity by adding logins

See the [Microsoft Teams Authentication guide](/docs/api-v2/authenticated-bots/teams) to get started.

## Google Meet Authenticated Bots

v2 lets bots join Google Meet as **authenticated Google Workspace users** via SAML SSO, instead of only as anonymous guests.

**v1**: Anonymous Meet joins only — bots failed on meetings restricted to signed-in or in-organization users and had to wait for manual admission.

**v2**:
- **Authenticated joins**: Bots sign in as a real Google Workspace user from a domain you control, so they can join meetings restricted to signed-in / organizational users.
- **Waiting-room bypass**: Invite a login's Google Group (`email_group`) to a meeting and the assigned bot lands in Meet's verified queue, skipping the waiting room.
- **Meet Workspaces & Meet Logins**: New `/v2/meet-workspaces` and `/v2/meet-logins` resources manage your SAML SSO configuration and the Workspace user identities bots sign in as. Meeting BaaS acts as the SAML IdP; keys are encrypted at rest with AES-256-GCM and the private key is never returned.
- **Round-robin pools**: Group logins by `email_group` and the dispatcher assigns the least-loaded active login (up to 20 concurrent sessions each by default). Add logins to scale capacity linearly.
- **Configurable fallback**: Per bot, choose to `fail` (default) or fall back to an `anonymous` join when the pool is saturated.
- **Utilization & alerts**: `GET /v2/meet-logins/utilization` reports live pool concurrency, with `Meet Login Utilization` and `Meet Login Unavailable` alert types to warn you before saturation.

### Benefits

- Record meetings that block anonymous guests
- Skip waiting rooms for fully unattended recording
- Scale authenticated capacity by adding logins, with visibility into headroom

See the [Google Meet Authentication guide](/docs/api-v2/authenticated-bots/meet) to get started.

## Enhanced Webhook Management

v2 provides enterprise-grade webhook management with multiple endpoints, signing, and rotation capabilities.

### Multiple Webhook Endpoints

**v1**: Single webhook URL per account

**v2**: 
- Create multiple webhook endpoints per team
- Each endpoint can subscribe to different event types
- Name and organize endpoints for better management
- Enable/disable endpoints without deletion
- Perfect for routing different events to different systems

### Webhook Security

**v1**: Basic webhook delivery

**v2**:
- **Webhook Signing**: All webhooks are cryptographically signed using SVIX
- **Secret Rotation**: Rotate webhook secrets without downtime
- **Message History**: View and resend failed webhook deliveries
- **Delivery Tracking**: Monitor webhook delivery status and retry failed messages

### Benefits

- Route different events to different systems (e.g., `bot.completed` to analytics, `calendar.event_created` to scheduling system)
- Rotate secrets for security compliance
- Debug webhook issues with message history
- Ensure reliable delivery with automatic retries

## Multiple API Keys

v2 enables better API key management for teams and applications.

### Key Features

**v1**: Single API key per account

**v2**:
- **Multiple API Keys**: Create multiple named API keys
- **Permission Types**: 
  - **Full Access**: Read, write, and delete operations
  - **Sending Access**: Write-only for bot creation endpoints (perfect for webhook-only integrations)

### Use Cases

- Separate keys for production and staging environments
- Create read-only keys for monitoring dashboards
- Use "Sending Access" keys for webhook-only integrations

## Teams-First Design

v2 is built around teams, enabling better collaboration and organization.

### Team Features

**v1**: Account-based (single user focus)

**v2**:
- **Team Organization**: All resources (bots, calendars, API keys) belong to teams
- **Team Members**: Invite team members with different roles (owner, admin, member)
- **Team Switching**: Switch between multiple teams in the dashboard
- **Team-Level Limits**: Daily bot caps, calendar limits, and rate limits are per-team
- **Team-Level Features**: Plans and features are configured per team

### Benefits

- Organize resources by project or department
- Collaborate with team members
- Manage multiple projects with separate teams
- Better access control and permissions

## Advanced Token Management

v2 provides comprehensive token management with transparency and automation.

### Automatic Token Refilling

**v1**: Manual token purchases

**v2**:
- **Auto-Refill**: Automatically purchase tokens when balance drops below threshold
- **Configurable Threshold**: Set your preferred minimum token balance
- **Token Pack Selection**: Choose which token pack to auto-purchase
- **Zero Downtime**: Never run out of tokens with automatic refilling

### Token Reminders

**v1**: No reminder system

**v2**:
- **Email Reminders**: Get notified when token balance is low
- **Configurable Threshold**: Set reminder threshold
- **Custom Email**: Configure reminder email address
- **Proactive Management**: Stay ahead of token depletion

### Token Consumption Transparency

**v1**: Limited visibility into token usage

**v2**:
- **Bot-Level Tracking**: See exact token consumption per bot
- **Breakdown by Type**: 
  - Recording tokens
  - Transcription tokens (or BYOK transcription tokens)
  - Streaming input/output tokens
- **Reserved Tokens**: See tokens reserved by active bots
- **Usage Dashboard**: Track total tokens consumed, available, and reserved

### Token Import from v1

**v1**: Tokens locked in v1 account

**v2**:
- **Flexible Import**: Import your remaining v1 tokens to v2 at your own pace - you can import multiple times as needed
- **Team-Based**: Imported tokens go to your team's balance
- **Flexible Amount**: Import all or a portion of your v1 tokens
- **Seamless Migration**: Continue using tokens without interruption

## Improved Transcription

v2 offers enhanced transcription capabilities with better models and more flexibility.

### Transcription Provider

**v1**: Basic transcription with limited options

**v2**:
- **Gladia Integration**: Enhanced transcription model with better accuracy
- **Custom Parameters**: Configure advanced transcription features:
  - **LLM Summaries**: Get AI-generated meeting summaries
  - **Language Detection**: Automatic language identification
  - **Translation**: Translate transcriptions to multiple languages
  - **Custom Vocabulary**: Improve accuracy for domain-specific terms
  - **Subtitles**: Generate subtitles in multiple formats
- **BYOK Support**: Use your own transcription provider API keys (saves tokens)

### Transcription Access

**v1**: Transcription embedded in webhook payloads

**v2**:
- **Raw Transcription**: Access complete provider response (includes LLM summaries, metadata)
- **Standardized Output**: Consistent transcription format across all providers
- **S3 Storage**: Secure storage of transcription with presigned URLs
- **Transcription IDs**: Track transcription jobs for BYOK users

## Standardized Request/Response Handling

v2 provides consistent, predictable API responses.

### Response Format

**v1**: Varied response structures across endpoints

**v2**:
- **Standardized Structure**: All responses follow `{success, data, error}` format
- **Consistent Error Format**: Uniform error responses with codes and messages
- **Better Error Codes**: Programmatic error codes (e.g., `FST_ERR_BOT_NOT_FOUND_BY_ID`)
- **Type Safety**: OpenAPI schemas for all endpoints

### Benefits

- Easier error handling in your code
- Better API documentation
- Consistent developer experience
- Type-safe integrations

## Integrated Support System

v2 includes a built-in support system for better customer service.

### Support Tickets

**v1**: External support channels

**v2**:
- **In-App Support**: Create support tickets directly from the dashboard
- **Bot-Specific Tickets**: Link tickets to specific bots for context
- **Screenshot Attachments**: Attach screenshots and files to tickets
- **Ticket Types**: 
  - Bug reports
  - Feature requests
  - General support
  - Billing inquiries
- **Status Tracking**: Track ticket status (open, in progress, resolved, closed)
- **Message Threads**: Maintain conversation history in tickets

### Benefits

- Faster support response times
- Better context with bot-specific tickets
- Feature request feedback loop
- Integrated support experience

## Invoice Management

v2 provides direct access to billing information.

### Invoice Features

**v1**: Invoices via email only

**v2**:
- **Dashboard Access**: View all invoices directly in the dashboard
- **Download PDFs**: Download invoice PDFs
- **Hosted Invoices**: Access Stripe-hosted invoice pages
- **Payment History**: Track payment status and history
- **Billing Information**: Manage billing details and payment methods

### Benefits

- Easy access to billing records
- Better expense tracking
- Simplified accounting
- Self-service billing management

## Automatic Data Deletion

v2 includes automatic data retention and deletion for compliance and cost management.

### Data Retention

**v1**: Manual data management

**v2**:
- **Automatic Deletion**: Data automatically deleted after retention period
- **Plan-Based Retention**: 
  - Pay-as-you-go: 3 days
  - Pro: 7 days
  - Scale: 14 days
  - Enterprise: 30 days
- **Bot Data Protection**: Bots with open support tickets are protected from deletion
- **Manual Deletion**: Delete data early via API if needed

### Benefits

- Compliance with data retention policies
- Automatic cleanup
- Protection for active support cases

## Calendar Integration for All Plans

v2 makes calendar integration available to all users, not just enterprise.

### Calendar Availability

**v1**: Calendar integration was enterprise-only

**v2**:
- **Pay-as-You-Go**: 2 calendar integrations included
- **Pro**: 10 calendar integrations
- **Scale**: 100 calendar integrations
- **Enterprise**: 1,000+ calendar integrations

### Enhanced Calendar Features

- **Bring-Your-Own-Credentials**: Use your own OAuth applications
- **Multiple Connections**: Connect multiple calendars per team
- **Better Sync**: Improved calendar event synchronization
- **Webhook Events**: Rich calendar webhook events for real-time updates

## Scheduled Bots Management

v2 provides full lifecycle management for scheduled bots.

### Scheduled Bot Features

**v1**: Basic scheduling with `start_time` parameter

**v2**:
- **Dedicated Endpoints**: Separate endpoints for scheduled bots
- **Update Support**: Modify scheduled bot configuration before execution
- **Delete Support**: Cancel scheduled bots before they execute
- **Status Tracking**: Track scheduled bot status (scheduled, active, completed, cancelled, failed)
- **Batch Operations**: Create multiple scheduled bots in one request

### Benefits

- Better control over scheduled meetings
- Update meetings that change
- Cancel unnecessary scheduled bots
- Manage recurring meetings efficiently

## Batch Operations

v2 enables efficient bulk operations.

### Batch Features

**v1**: One bot per request

**v2**:
- **Batch Bot Creation**: Create up to 100 bots in a single request
- **Batch Scheduled Bots**: Create multiple scheduled bots at once
- **Partial Success**: Get detailed results for each bot in the batch
- **Error Mapping**: Map errors to specific items in the batch

### Benefits

- Faster bulk operations
- Reduced API calls
- Better error handling
- Efficient onboarding workflows

## Enhanced Error Handling

v2 provides detailed, actionable error information.

### Error Improvements

**v1**: Generic error messages

**v2**:
- **Standardized Error Codes**: Consistent error codes (e.g., `FST_ERR_INSUFFICIENT_TOKENS`)
- **Detailed Messages**: Human-readable error messages
- **Error Context**: Additional error details when available
- **HTTP Status Codes**: Proper HTTP status codes for each error type

### Bot Process Errors

v2 includes comprehensive bot process error documentation:
- Normal end reasons (e.g., `BOT_REMOVED`, `NO_ATTENDEES`)
- Error end reasons (e.g., `BOT_NOT_ACCEPTED`, `TIMEOUT_WAITING_TO_START`)
- Transcription errors (e.g., `TRANSCRIPTION_FAILED`)
- Platform-specific errors (Zoom, Meet, Teams)

## Improved Deduplication

v2 provides better protection against duplicate bots.

### Deduplication Features

**v1**: Basic deduplication

**v2**:
- **Configurable**: Control deduplication per bot with `allow_multiple_bots` flag
- **Lock Window**: 4-minute lock window prevents race conditions
- **Clear Errors**: `BOT_ALREADY_EXISTS` error with helpful message
- **Fail-Open**: System continues to work even if deduplication check fails

## Rate Limiting Transparency

v2 provides clear rate limiting with per-team configuration.

### Rate Limiting Features

**v1**: Limited rate limiting visibility

**v2**:
- **Per-Team Limits**: Rate limits configured per team/plan
- **Per-Second Limits**: Clear per-second rate limit configuration
- **GET Request Exclusion**: GET requests don't count toward rate limits
- **Clear Error Messages**: `FST_ERR_TOO_MANY_REQUESTS` with retry information
- **Dashboard Display**: See rate limits in the dashboard

## Better Developer Experience

v2 focuses on making integration easier and more reliable.

### API Improvements

- **Better Documentation**: Comprehensive guides and examples
- **Error Codes**: Programmatic error handling
- **Webhook Reliability**: Message history and resend capabilities

### Dashboard Features

- **Bot Details**: Comprehensive bot information with token breakdown
- **Status History**: Track bot status changes over time
- **Artifact Management**: Easy access to recordings, transcriptions, and screenshots
- **Support Integration**: Create support tickets directly from bot details
- **Team Management**: Easy team switching and member management

## Summary

v2 represents a significant upgrade in:

- **Security**: Webhook signing, secret rotation, multiple API keys
- **Transparency**: Bot-level token tracking, detailed error codes, status history
- **Automation**: Auto-refill, token reminders, automatic data deletion
- **Flexibility**: Multiple webhooks, multiple API keys, team organization
- **Accessibility**: Calendar integration on all plans, better support system
- **Developer Experience**: Standardized responses, better errors, comprehensive documentation

These improvements make v2 the clear choice for new integrations and provide compelling reasons to migrate from v1.



---

## Streaming

Real-time audio streaming with bidirectional WebSocket support

### Source: ./content/docs/api-v2/streaming.mdx


Meeting BaaS v2 supports real-time audio streaming over WebSocket, allowing you to receive meeting audio as it happens and optionally send audio back into the meeting. This enables use cases like live transcription, real-time translation, AI-powered meeting assistants, and speaking bots.

## Overview

Streaming provides:

- **Output Streaming**: Receive the meeting's mixed audio in real time via WebSocket
- **Input Streaming**: Send audio into the meeting so participants can hear it (for speaking bots, AI agents, etc.)
- **Bidirectional Streaming**: Combine both - receive meeting audio and speak back - using a single or two separate WebSocket connections
- **Managed Real-Time Transcription**: Let Meeting BaaS run real-time speech-to-text and stream transcript events to your WebSocket endpoint, with a choice of providers
- **Speaker Diarization**: Receive real-time speaker state updates as JSON messages alongside the audio stream
- **Configurable Sample Rate**: Choose from 16,000 Hz, 24,000 Hz (default), 32,000 Hz, or 48,000 Hz
- **Works on All Platforms**: Google Meet, Microsoft Teams, and Zoom

## Enabling Streaming

To enable streaming, include `streaming_enabled` and `streaming_config` in your bot creation request:

```json
{
  "meeting_url": "https://meet.google.com/abc-defg-hij",
  "bot_name": "AI Assistant",
  "streaming_enabled": true,
  "streaming_config": {
    "output_url": "wss://your-server.com/audio-stream",
    "input_url": null,
    "audio_frequency": 16000
  }
}
```

### Configuration Fields

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `mode` | `string` | `audio` | Streaming mode. `audio` streams raw audio over WebSocket; `transcription` runs managed real-time speech-to-text and streams JSON transcript events to `output_url` over WebSocket |
| `output_url` | `string \| null` | `null` | When `mode` is `audio`: WebSocket URL where the bot sends meeting audio (optional). When `mode` is `transcription`: WebSocket URL where the bot sends transcript events as JSON messages - **required and non-null** in this mode |
| `input_url` | `string \| null` | `null` | WebSocket URL from which the bot receives audio to play into the meeting |
| `audio_frequency` | `integer` | `24000` | Sample rate in Hz. Supported: `16000`, `24000`, `32000`, `48000` |
| `transcription` | `object \| null` | `null` | Real-time STT provider configuration. Required when `mode` is `transcription` (see [Managed Real-Time Transcription](#managed-real-time-transcription)) |

<Callout type="info">
  In `audio` mode, provide `output_url` to receive meeting audio, `input_url` to send audio into the meeting, or both for bidirectional streaming - set either to `null` if you only need one direction. In `transcription` mode, `output_url` is required (bot creation fails without it) and receives JSON transcript messages instead of raw audio.
</Callout>

## Streaming Modes

### Output Only (Receive Meeting Audio)

Use this mode when you want to process meeting audio in real time - for example, to feed it into your own transcription engine, AI model, or analytics pipeline.

```json
{
  "streaming_enabled": true,
  "streaming_config": {
    "output_url": "wss://your-server.com/audio-stream",
    "input_url": null,
    "audio_frequency": 24000
  }
}
```

Your WebSocket server receives:
- A **handshake message** (JSON) when the connection opens
- **Binary audio chunks** (raw PCM) every 100ms
- **Speaker state updates** (JSON) when speakers change

### Input Only (Send Audio into the Meeting)

Use this mode when you want to inject audio into the meeting without processing the output - for example, playing pre-recorded announcements or TTS audio.

```json
{
  "streaming_enabled": true,
  "streaming_config": {
    "output_url": null,
    "input_url": "wss://your-server.com/audio-input",
    "audio_frequency": 24000
  }
}
```

Your WebSocket server sends binary audio chunks to the bot, and participants in the meeting hear the audio.

### Bidirectional (Receive and Send Audio)

Use this mode for interactive AI agents and speaking bots. The bot receives meeting audio, you process it (e.g., speech-to-text → LLM → text-to-speech), and send audio back.

**Option A: Same URL for both directions**

When `input_url` and `output_url` are the same, the bot uses a single bidirectional WebSocket connection:

```json
{
  "streaming_enabled": true,
  "streaming_config": {
    "output_url": "wss://your-server.com/audio",
    "input_url": "wss://your-server.com/audio",
    "audio_frequency": 24000
  }
}
```

**Option B: Separate URLs**

When the URLs differ, the bot opens two separate WebSocket connections - one for sending audio to your server, and one for receiving audio from your server:

```json
{
  "streaming_enabled": true,
  "streaming_config": {
    "output_url": "wss://your-server.com/audio-out",
    "input_url": "wss://your-server.com/audio-in",
    "audio_frequency": 24000
  }
}
```

### Managed Real-Time Transcription

Set `mode` to `"transcription"` to have Meeting BaaS run real-time speech-to-text for you and **stream transcript events to your `output_url` over WebSocket** as the meeting happens - no need to run your own STT engine on the audio stream.

```json
{
  "streaming_enabled": true,
  "streaming_config": {
    "mode": "transcription",
    "output_url": "wss://your-server.com/transcripts",
    "transcription": {
      "provider": "gladia",
      "api_key": null,
      "custom_params": null,
      "region": null
    }
  }
}
```

The `streaming_config.transcription` object configures the real-time STT provider:

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `provider` | `string` | `gladia` | Real-time STT provider: `gladia`, `deepgram`, `assemblyai`, `speechmatics`, `soniox`, or `elevenlabs` (streaming-only) |
| `api_key` | `string \| null` | `null` | Your provider API key (BYOK). Leave `null` to use the platform key |
| `custom_params` | `object \| null` | `null` | Provider-specific advanced options, forwarded to the provider's **live session API** (see [Custom Parameters](#custom-parameters-live-vs-batch)) |
| `region` | `string \| null` | `null` | Provider API region. When omitted, provider defaults apply (`gladia=eu-west`, `deepgram=eu`, `assemblyai=eu`, `speechmatics=eu1`, `soniox=us`, `elevenlabs=global`) |

<Callout type="info">
  All [batch transcription providers](/docs/api-v2/transcription#transcription-providers) are available for real-time streaming, plus **ElevenLabs**, which is streaming-only. In `transcription` mode, `output_url` is still a **WebSocket** endpoint (`wss://`) - the bot opens a WebSocket connection to it and sends JSON text messages. It does not send HTTP POST requests.
</Callout>

#### Custom Parameters (live vs. batch)

`custom_params` is forwarded to the provider's **live session API** - for Gladia, that is [`POST /v2/live`](https://docs.gladia.io/api-reference/v2/live/init), not the [pre-recorded API](https://docs.gladia.io/api-reference/v2/pre-recorded/init) used for [batch transcription](/docs/api-v2/transcription). The two APIs accept **different shapes**, and reusing batch-shaped params is the most common mistake: for example, Gladia's live API nests translation under `realtime_processing`, while the batch API takes `translation_config` at the top level.

```json
{
  "streaming_config": {
    "mode": "transcription",
    "output_url": "wss://your-server.com/transcripts",
    "transcription": {
      "provider": "gladia",
      "custom_params": {
        "language_config": { "languages": ["ru"] },
        "realtime_processing": {
          "translation": true,
          "translation_config": { "target_languages": ["en", "de", "it"] }
        }
      }
    }
  }
}
```

`custom_params` is validated against the provider's live schema when you create the bot - unknown fields are rejected with a `400` that points at the correct live-API location where one exists (e.g. `translation_config` → `realtime_processing.translation_config`).

The same batch-vs-live distinction applies to every provider - always use the provider's **real-time/streaming** parameter reference, not the pre-recorded one:

| Provider | Live API parameters |
|----------|--------------------|
| Gladia | [Live init](https://docs.gladia.io/api-reference/v2/live/init) |
| Deepgram | [Streaming API](https://developers.deepgram.com/docs/streaming) |
| AssemblyAI | [Streaming Speech-to-Text](https://www.assemblyai.com/docs/speech-to-text/streaming) |
| Speechmatics | [Real-Time API](https://docs.speechmatics.com/rt-api-ref) |
| Soniox | [Real-Time API](https://soniox.com/docs) |
| ElevenLabs | [Speech-to-Text](https://elevenlabs.io/docs/capabilities/speech-to-text) (params not validated at creation - errors surface via the `error` event) |

<Callout type="warn">
  `encoding`, `sample_rate`, `bit_depth` and `channels` are set by the platform and cannot be overridden through `custom_params`. To control the audio sample rate, use `streaming_config.audio_frequency`.
</Callout>

#### Transcription Session Events

In `transcription` mode, the bot sends three event types to `output_url`, all sharing the `{ "event", "bot_id", "data" }` envelope:

| Event | When | `data` |
|-------|------|--------|
| `session.started` | The provider transcription session is live - transcript segments will follow | `{ "provider": "gladia" }` |
| `transcript.segment` | One per transcript piece (partial and final) | See [Transcript Events](#transcript-events) |
| `error` | The transcription session failed to start or died mid-meeting | `{ "code": "transcription_session_failed", "message": "..." }` |

If you receive `error`, live transcription is down for the rest of the meeting - the `message` field carries the provider's reason (e.g. rejected parameters). Recording and [batch transcription](/docs/api-v2/transcription) are unaffected. Treat a connection that never receives `session.started` as not yet live rather than silent.

The bot also sends standard WebSocket ping frames roughly every 30 seconds so intermediaries (e.g. Cloudflare tunnels, which drop idle connections after ~100s) keep the connection open through quiet stretches of the meeting. Most WebSocket libraries answer pings automatically - no action needed.

#### Transcript Events

Your WebSocket server receives JSON text messages, one per transcript segment. Every message has the same envelope:

```json
{
  "event": "transcript.segment",
  "bot_id": "123e4567-e89b-12d3-a456-426614174000",
  "data": {
    "text": "Hello everyone, let's get started.",
    "isFinal": true,
    "utteranceStart": 12.34,
    "utteranceEnd": 15.02,
    "confidence": 0.97,
    "words": [
      { "text": "Hello", "start": 12.34, "end": 12.61, "confidence": 0.98 }
    ],
    "speaker": { "name": "John Doe", "id": 1 }
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `event` | `string` | `"transcript.segment"` for transcript messages (see [Transcription Session Events](#transcription-session-events) for the other event types) |
| `bot_id` | `string` | UUID of the bot |
| `data.text` | `string` | Transcribed text for this segment |
| `data.isFinal` | `boolean` | `false` for partial (interim) segments, `true` for final segments (see [Partial vs. Final Segments](#partial-vs-final-segments)) |
| `data.utteranceStart` | `number` | Utterance start time in seconds |
| `data.utteranceEnd` | `number` | Utterance end time in seconds |
| `data.confidence` | `number` | Overall confidence score for the segment (0-1), when the provider reports one |
| `data.words` | `array` | Word-level timings: `{ text, start, end, confidence?, speaker? }` |
| `data.speaker` | `object \| null` | Active speaker at the time of the segment: `{ name, id }`, or `null` when unknown |

The `event` and `bot_id` envelope fields are always present. Fields under `data` are provider-dependent - treat all of them as optional.

#### Partial vs. Final Segments

Messages carry no explicit segment or utterance identifier. Partial segments (`isFinal: false`) are progressive snapshots of the utterance currently being spoken - each new partial for that utterance replaces the previous one, and the final segment (`isFinal: true`) supersedes all partials for it. Correlate them by time: partials and their final cover overlapping `utteranceStart`/`utteranceEnd` ranges. The simplest robust approach is to use partials for live display only (always replacing the last partial shown) and build your stored transcript exclusively from `isFinal: true` segments.

If the WebSocket connection drops, the bot reconnects with exponential backoff (1s doubling up to 60s) and buffers up to 100 transcript events while disconnected, flushing them on reconnect. Events beyond the buffer limit are dropped.

## WebSocket Protocol

### Connection Lifecycle

1. The bot joins the meeting and establishes WebSocket connection(s) to your server
2. Immediately sends a **handshake message** (JSON text) on the output connection
3. Begins streaming **binary audio chunks** every 100ms
4. Sends **speaker state updates** (JSON text) whenever the active speakers change
5. On the input connection, the bot listens for **binary audio chunks** from your server
6. When the meeting ends or the bot leaves, the WebSocket connections close

### Handshake Message

When the output WebSocket connection opens, the bot sends a JSON text message:

```json
{
  "protocol_version": 2,
  "bot_id": "123e4567-e89b-12d3-a456-426614174000",
  "offset": 0.0,
  "sample_rate": 24000,
  "start_time": null
}
```

| Field | Type | Description |
|-------|------|-------------|
| `protocol_version` | `number` | Protocol version. May vary by meeting platform (currently `1` or `2`). Treat as informational - do not depend on a specific value. |
| `bot_id` | `string` | UUID of the bot |
| `offset` | `number` | Time offset in seconds. Currently always `0.0`: every connection (including reconnects) starts a fresh audio stream, so audio timing should be computed from the handshake receipt time plus the cumulative sample count. |
| `sample_rate` | `number` | The audio sample rate in Hz, matching your `audio_frequency` config |
| `start_time` | `number` or `null` | Epoch **milliseconds** when audio capture started, or `null` if capture has not started yet. The handshake is sent again with the updated value as soon as capture starts, before the first audio chunk. |

Use this message to initialize your audio processing pipeline with the correct sample rate and to associate the stream with a specific bot.

To timestamp the audio stream: `audio_time_ms = handshake_receipt_time_ms + (offset + cumulative_samples / sample_rate) * 1000`. Audio capture starts when the bot opens the meeting page, which is later than `joined_at` in the bot details. On reconnection the bot sends a new handshake with `offset` reset to `0.0`, so restart the clock; audio during the disconnection is dropped.

### Output Audio Chunks (Bot → Your Server)

After the handshake, the bot sends **binary WebSocket messages** containing raw audio data:

| Property | Value |
|----------|-------|
| **Format** | Signed 16-bit PCM |
| **Channels** | Mono (1 channel) |
| **Sample Rate** | As configured in `audio_frequency` (default 24,000 Hz) |
| **Chunk Duration** | 100ms |
| **Samples per Chunk** | `audio_frequency / 10` (e.g., 2,400 at 24kHz) |
| **Bytes per Chunk** | `samples × 2` (e.g., 4,800 bytes at 24kHz) |

The audio is the mixed meeting audio - all participants' audio combined into a single mono stream. Each chunk represents exactly 100 milliseconds of audio.

<Callout type="info">
  The binary messages contain **raw PCM samples only** - no headers, framing, or metadata. Each message is a sequence of signed 16-bit integers representing audio samples.
</Callout>

### Speaker State Updates (Bot → Your Server)

Alongside audio chunks, the bot sends **JSON text messages** with real-time speaker information whenever the active speakers change:

```json
[
  {
    "name": "John Doe",
    "id": 1,
    "timestamp": 1788284782279,
    "isSpeaking": true
  },
  {
    "name": "Jane Smith",
    "id": 2,
    "timestamp": 1788284782279,
    "isSpeaking": false
  }
]
```

| Field | Type | Description |
|-------|------|-------------|
| `name` | `string` | Participant's display name |
| `id` | `number` or `null` | Sequential participant ID (stable within a session) |
| `timestamp` | `number` | Unix timestamp in **milliseconds** (the moment the speaker state was observed) |
| `isSpeaking` | `boolean` | Whether the participant is currently speaking |

These updates are sent on the **output WebSocket** as JSON text messages. Your server can distinguish them from audio chunks by checking the WebSocket message type: **text** messages are speaker state, **binary** messages are audio.

### Input Audio Chunks (Your Server → Bot)

To send audio into the meeting, your server sends **binary WebSocket messages** on the input connection:

| Property | Value |
|----------|-------|
| **Format** | Signed 16-bit PCM |
| **Channels** | Mono (1 channel) |
| **Sample Rate** | Must match the configured `audio_frequency` |

The bot receives these chunks and plays them into the meeting - all participants will hear the audio. There is no strict chunk size requirement for input audio, but sending in consistent intervals (e.g., every 20-100ms) produces the smoothest playback.

<Callout type="warning">
  The input audio sample rate **must match** the `audio_frequency` you configured. Mismatched sample rates will cause audio distortion.
</Callout>

## Reconnection

The bot automatically reconnects to your WebSocket server if the connection drops:

- Uses **exponential backoff**: 1s, 2s, 4s, 8s, ... up to 60s maximum
- Resends the **handshake message** after reconnecting
- Audio chunks sent during disconnection are **not buffered** - they are dropped

Your WebSocket server should be prepared to receive a new handshake message at any time, indicating a reconnection.

## Examples

### Creating a Bot with Output Streaming

<Tabs items={['Bash', 'Python', 'JavaScript']}>
  <Tab value="Bash">
    ```bash
    curl -X POST "https://api.meetingbaas.com/v2/bots" \
         -H "Content-Type: application/json" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY" \
         -d '{
               "meeting_url": "https://meet.google.com/abc-defg-hij",
               "bot_name": "Audio Listener",
               "streaming_enabled": true,
               "streaming_config": {
                 "output_url": "wss://your-server.com/audio-stream",
                 "input_url": null,
                 "audio_frequency": 24000
               }
             }'
    ```
  </Tab>
  <Tab value="Python">
    ```python
    import requests

    response = requests.post(
        "https://api.meetingbaas.com/v2/bots",
        headers={
            "Content-Type": "application/json",
            "x-meeting-baas-api-key": "YOUR-API-KEY",
        },
        json={
            "meeting_url": "https://meet.google.com/abc-defg-hij",
            "bot_name": "Audio Listener",
            "streaming_enabled": True,
            "streaming_config": {
                "output_url": "wss://your-server.com/audio-stream",
                "input_url": None,
                "audio_frequency": 24000,
            },
        },
    )
    print(response.json())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript
    fetch("https://api.meetingbaas.com/v2/bots", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
      },
      body: JSON.stringify({
        meeting_url: "https://meet.google.com/abc-defg-hij",
        bot_name: "Audio Listener",
        streaming_enabled: true,
        streaming_config: {
          output_url: "wss://your-server.com/audio-stream",
          input_url: null,
          audio_frequency: 24000,
        },
      }),
    })
      .then((response) => response.json())
      .then((data) => console.log(data.data.bot_id));
    ```
  </Tab>
</Tabs>

### Creating a Bidirectional Speaking Bot

<Tabs items={['Bash', 'Python', 'JavaScript']}>
  <Tab value="Bash">
    ```bash
    curl -X POST "https://api.meetingbaas.com/v2/bots" \
         -H "Content-Type: application/json" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY" \
         -d '{
               "meeting_url": "https://meet.google.com/abc-defg-hij",
               "bot_name": "AI Meeting Assistant",
               "streaming_enabled": true,
               "streaming_config": {
                 "output_url": "wss://your-server.com/audio",
                 "input_url": "wss://your-server.com/audio",
                 "audio_frequency": 24000
               }
             }'
    ```
  </Tab>
  <Tab value="Python">
    ```python
    import requests

    response = requests.post(
        "https://api.meetingbaas.com/v2/bots",
        headers={
            "Content-Type": "application/json",
            "x-meeting-baas-api-key": "YOUR-API-KEY",
        },
        json={
            "meeting_url": "https://meet.google.com/abc-defg-hij",
            "bot_name": "AI Meeting Assistant",
            "streaming_enabled": True,
            "streaming_config": {
                "output_url": "wss://your-server.com/audio",
                "input_url": "wss://your-server.com/audio",
                "audio_frequency": 24000,
            },
        },
    )
    print(response.json())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript
    fetch("https://api.meetingbaas.com/v2/bots", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
      },
      body: JSON.stringify({
        meeting_url: "https://meet.google.com/abc-defg-hij",
        bot_name: "AI Meeting Assistant",
        streaming_enabled: true,
        streaming_config: {
          output_url: "wss://your-server.com/audio",
          input_url: "wss://your-server.com/audio",
          audio_frequency: 24000,
        },
      }),
    })
      .then((response) => response.json())
      .then((data) => console.log(data.data.bot_id));
    ```
  </Tab>
</Tabs>

### WebSocket Server (Receiving Audio)

Here's how to build a WebSocket server that receives and processes the stream:

<Tabs items={['Python', 'JavaScript']}>
  <Tab value="Python">
    ```python
    import asyncio
    import json
    import numpy as np
    import websockets

    async def handle_stream(websocket):
        async for message in websocket:
            if isinstance(message, str):
                # JSON message - either handshake or speaker state
                data = json.loads(message)

                if "protocol_version" in data:
                    # Handshake message
                    print(f"Bot connected: {data['bot_id']}")
                    print(f"Sample rate: {data['sample_rate']} Hz")
                else:
                    # Speaker state update
                    for speaker in data:
                        status = "speaking" if speaker["isSpeaking"] else "silent"
                        print(f"{speaker['name']}: {status}")

            elif isinstance(message, bytes):
                # Binary message - raw Int16 PCM audio
                audio = np.frombuffer(message, dtype=np.int16)
                print(f"Audio chunk: {len(audio)} samples, "
                      f"duration: {len(audio) / 24000 * 1000:.0f}ms")

                # Process the audio (e.g., feed to STT, analyze, store)
                # audio is a numpy array of signed 16-bit integers

    async def main():
        async with websockets.serve(handle_stream, "0.0.0.0", 8765):
            print("WebSocket server running on ws://0.0.0.0:8765")
            await asyncio.Future()  # Run forever

    asyncio.run(main())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript
    const { WebSocketServer } = require("ws");

    const wss = new WebSocketServer({ port: 8765 });

    wss.on("connection", (ws) => {
      console.log("Bot connected");

      ws.on("message", (message, isBinary) => {
        if (!isBinary) {
          // JSON message - either handshake or speaker state
          const data = JSON.parse(message.toString());

          if (data.protocol_version) {
            // Handshake message
            console.log(`Bot ID: ${data.bot_id}`);
            console.log(`Sample rate: ${data.sample_rate} Hz`);
          } else {
            // Speaker state update
            data.forEach((speaker) => {
              const status = speaker.isSpeaking ? "speaking" : "silent";
              console.log(`${speaker.name}: ${status}`);
            });
          }
        } else {
          // Binary message - raw Int16 PCM audio
          const audio = new Int16Array(
            message.buffer,
            message.byteOffset,
            message.byteLength / 2
          );
          console.log(
            `Audio chunk: ${audio.length} samples, ` +
            `duration: ${(audio.length / 24000) * 1000}ms`
          );

          // Process the audio (e.g., feed to STT, analyze, store)
        }
      });

      ws.on("close", () => console.log("Bot disconnected"));
    });

    console.log("WebSocket server running on ws://0.0.0.0:8765");
    ```
  </Tab>
</Tabs>

### WebSocket Server (Bidirectional)

For a bidirectional setup where you receive audio, process it, and send audio back:

<Tabs items={['Python', 'JavaScript']}>
  <Tab value="Python">
    ```python
    import asyncio
    import json
    import numpy as np
    import websockets

    SAMPLE_RATE = 24000

    async def handle_bidirectional(websocket):
        async for message in websocket:
            if isinstance(message, str):
                data = json.loads(message)
                if "protocol_version" in data:
                    print(f"Bot connected: {data['bot_id']}")
                    continue
                # Speaker state update
                for speaker in data:
                    if speaker["isSpeaking"]:
                        print(f"Now speaking: {speaker['name']}")
                continue

            # Binary audio from the meeting
            audio_in = np.frombuffer(message, dtype=np.int16)

            # --- Your processing pipeline here ---
            # Example: speech-to-text → LLM → text-to-speech
            # audio_out = your_pipeline(audio_in)

            # Send audio back into the meeting (Int16 PCM)
            # await websocket.send(audio_out.tobytes())

    async def main():
        async with websockets.serve(handle_bidirectional, "0.0.0.0", 8765):
            print("Bidirectional WebSocket server on ws://0.0.0.0:8765")
            await asyncio.Future()

    asyncio.run(main())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript
    const { WebSocketServer } = require("ws");

    const SAMPLE_RATE = 24000;
    const wss = new WebSocketServer({ port: 8765 });

    wss.on("connection", (ws) => {
      console.log("Bot connected");

      ws.on("message", (message, isBinary) => {
        if (!isBinary) {
          const data = JSON.parse(message.toString());
          if (data.protocol_version) {
            console.log(`Bot ID: ${data.bot_id}`);
            return;
          }
          // Speaker state update
          data.forEach((s) => {
            if (s.isSpeaking) console.log(`Now speaking: ${s.name}`);
          });
          return;
        }

        // Binary audio from the meeting
        const audioIn = new Int16Array(
          message.buffer,
          message.byteOffset,
          message.byteLength / 2
        );

        // --- Your processing pipeline here ---
        // Example: speech-to-text → LLM → text-to-speech
        // const audioOut = yourPipeline(audioIn);

        // Send audio back into the meeting (Int16 PCM)
        // ws.send(Buffer.from(audioOut.buffer));
      });

      ws.on("close", () => console.log("Bot disconnected"));
    });

    console.log("Bidirectional WebSocket server on ws://0.0.0.0:8765");
    ```
  </Tab>
</Tabs>

## Combining Streaming with Recording and Transcription

Streaming works independently from recording and transcription. You can enable all three at once:

```json
{
  "meeting_url": "https://meet.google.com/abc-defg-hij",
  "bot_name": "Full-Featured Bot",
  "recording_mode": "speaker_view",
  "transcription_enabled": true,
  "transcription_config": {
    "provider": "gladia"
  },
  "streaming_enabled": true,
  "streaming_config": {
    "output_url": "wss://your-server.com/audio-stream",
    "input_url": null,
    "audio_frequency": 24000
  }
}
```

The recording, transcription, and streaming pipelines operate independently - enabling streaming does not affect recording quality or transcription accuracy.

## Error Handling

Currently, we don't give any feedback on errors with the websocket connection or invalid message formats. We plan to improve this in the future.

**Troubleshooting:**

- Verify your WebSocket server is running and accessible from the internet
- Ensure the URL uses `wss://` for secure WebSocket connections
- Check that your server accepts WebSocket upgrade requests
- Verify there are no firewall rules blocking the connection

### Connection Drops

If the WebSocket connection drops during a meeting, the bot will automatically attempt to reconnect with exponential backoff. Audio chunks during the disconnection period are lost and not buffered.

Your server should handle reconnection gracefully - when the bot reconnects, it sends a fresh handshake message.

## Best Practices

1. **Use `wss://` endpoints**: We recommend using secure WebSocket connections with valid TLS certificates.
2. **Handle reconnections**: Your server should accept new handshake messages at any time, as the bot reconnects automatically on connection drops.
3. **Process audio asynchronously**: Audio chunks arrive every 100ms. Ensure your processing pipeline can keep up to avoid backpressure.
4. **Match sample rates**: When sending audio back (input streaming), always use the same sample rate configured in `audio_frequency`. Mismatched rates cause distorted audio.
5. **Distinguish message types**: Use the WebSocket message type to differentiate - **binary** for audio, **text** for JSON (handshake and speaker state).
6. **Keep connections alive**: The bot expects the WebSocket connection to remain open. Avoid closing the connection from your server while the meeting is active.
7. **Monitor speaker state**: Use speaker state updates to know who is talking - this is useful for building real-time diarization or triggering AI responses to specific speakers.

## Frequently Asked Questions

<Accordions>
  <Accordion title="What audio format does the stream use?">
    The stream uses **raw signed 16-bit PCM audio** (mono). There are no headers or container formats - each binary WebSocket message is a sequence of Int16 samples. This is the same format used by most audio processing libraries and speech-to-text APIs.
  </Accordion>

  <Accordion title="Can I use streaming without recording?">
    Yes. Streaming and recording are independent features. You can set `recording_mode` to `"audio_only"` to minimize resource usage while still receiving the full audio stream in real time.
  </Accordion>

  <Accordion title="How do I distinguish audio messages from speaker state messages?">
    Check the WebSocket message type. **Binary** messages are audio chunks (raw PCM data). **Text** messages are JSON - either a handshake (contains `protocol_version`) or a speaker state update (a JSON array of speaker objects).
  </Accordion>

  <Accordion title="What happens if my WebSocket server goes down during a meeting?">
    The bot automatically reconnects with exponential backoff (1s → 2s → 4s → ... up to 60s). Audio during the disconnection is dropped. When the connection is re-established, the bot sends a new handshake message and resumes streaming. The recording (if enabled) is not affected by streaming connection issues.
  </Accordion>

  <Accordion title="Can I change the chunk size?">
    No. Audio chunks are fixed at 100ms intervals. This provides a good balance between latency and overhead. If your use case requires different buffering, implement it on your server side.
  </Accordion>

  <Accordion title="Which sample rate should I choose?">
    - **16,000 Hz**: Lowest bandwidth. Sufficient for basic speech recognition. Good for constrained environments.
    - **24,000 Hz** (default): Good balance of quality and bandwidth. Works well with most speech-to-text APIs.
    - **32,000 Hz**: Higher fidelity. Useful if your processing pipeline benefits from more audio detail.
    - **48,000 Hz**: Studio quality. Highest bandwidth usage. Use only if your pipeline specifically requires it.

    Most speech-to-text services (Deepgram, AssemblyAI, Whisper) work well with 16-24kHz audio, so the default of 24kHz is recommended for most use cases.
  </Accordion>

  <Accordion title="Is the audio mixed or per-speaker?">
    The streamed audio is **mixed** - all participants' audio is combined into a single mono stream. To identify who is speaking, use the **speaker state updates** sent alongside the audio. If you need per-speaker audio, you can use the speaker state timestamps to segment the mixed audio by speaker. Note that speaker timestamps are in **milliseconds**; compute your audio timeline in the same unit (see the handshake `offset` documentation).
  </Accordion>

  <Accordion title="Can I use the same WebSocket URL for input and output?">
    Yes. When `input_url` and `output_url` are the same, the bot uses a single bidirectional WebSocket connection. Your server receives audio and speaker state on the same connection, and can send audio back on the same connection. This is the simplest setup for bidirectional use cases.
  </Accordion>

  <Accordion title="What latency can I expect?">
    Audio chunks are sent every 100ms. Combined with WebSocket transport overhead, expect approximately 100-200ms of latency from when audio is captured in the meeting to when it arrives at your server. For bidirectional use cases, the round-trip latency (meeting → your server → back to meeting) depends primarily on your processing pipeline speed.
  </Accordion>

  <Accordion title="Does streaming work on all meeting platforms?">
    Yes. Streaming works on Google Meet, Microsoft Teams, and Zoom. The WebSocket protocol and audio format are identical across all platforms - your server implementation does not need to be platform-aware.
  </Accordion>

  <Accordion title="Can I update the streaming configuration after the bot is created?">
    Streaming configuration is set when creating the bot and cannot be changed while the bot is running. To use different streaming settings, create a new bot with the desired configuration.
  </Accordion>

  <Accordion title="Does the bot buffer audio if my server is slow to process?">
    No. Audio chunks are sent in real time and are not buffered. If your server cannot keep up with the 100ms chunk interval, chunks may queue up at the WebSocket layer. Ensure your processing pipeline can handle the incoming data rate to avoid growing memory usage or dropped frames.
  </Accordion>

  <Accordion title="How much bandwidth does streaming use?">
    Bandwidth depends on the sample rate:

    - **16,000 Hz**: ~32 KB/s (256 kbps)
    - **24,000 Hz**: ~48 KB/s (384 kbps)
    - **32,000 Hz**: ~64 KB/s (512 kbps)
    - **48,000 Hz**: ~96 KB/s (768 kbps)

    These are approximate values for the raw audio stream (mono, 16-bit PCM). Actual bandwidth is slightly higher due to WebSocket framing overhead.
  </Accordion>
</Accordions>

## Next Steps

- [Send a Bot](/docs/api-v2/getting-started/sending-a-bot) to get started with the API
- Set up [Webhooks](/docs/api-v2/webhooks) to receive bot status notifications
- Explore [Speaking Bots](/docs/speaking-bots) for a ready-made AI meeting agent framework
- Check the [API Reference](/docs/api-v2/reference) for complete parameter documentation


---

## Transcription

Complete guide to transcription features, custom parameters, and BYOK

### Source: ./content/docs/api-v2/transcription.mdx


Meeting BaaS v2 provides powerful transcription capabilities with support for custom parameters, multiple providers, and Bring Your Own Key (BYOK) options.

## Overview

Transcription in v2 offers:

- **Multiple Providers**: Choose from Gladia (default), Deepgram, AssemblyAI, Speechmatics, and Soniox — plus ElevenLabs for real-time streaming
- **BYOK Support**: Use your own transcription provider API keys to save on token costs
- **Custom Parameters**: Configure LLM summaries, translation, language detection, and more
- **Raw & Processed Output**: Access both raw provider responses and standardized transcriptions
- **Transcription IDs**: Track transcription jobs for BYOK users

## Enabling Transcription

To enable transcription for a bot, include `transcription_config` in your bot creation request:

```json
{
  "meeting_url": "https://meet.google.com/...",
  "bot_name": "AI Notetaker",
  "transcription_enabled": true,
  "transcription_config": {
    "provider": "gladia",
    "api_key": null,
    "custom_params": null
  }
}
```

### Basic Configuration

**Required Fields:**
- `transcription_enabled`: Set to `true` to enable transcription
- `transcription_config.provider`: One of `"gladia"` (default), `"deepgram"`, `"assemblyai"`, `"speechmatics"`, or `"soniox"`.

**Optional Fields:**
- `transcription_config.api_key`: Your transcription provider API key (for BYOK - see below)
- `transcription_config.custom_params`: Custom parameters for advanced features (see below)

## Transcription Providers

Select a provider via the `provider` field in `transcription_config` (batch) or `streaming_config.transcription` (real-time streaming).

### Batch Transcription

- **Gladia** (default) — high-accuracy transcription with speaker diarization, multi-language support, and advanced features (summarization, translation, etc.)
- **Deepgram**
- **AssemblyAI**
- **Speechmatics**
- **Soniox**

### Real-Time Streaming

In addition to all of the batch providers above, real-time streaming transcription also supports:

- **ElevenLabs**

<Callout type="warn">
  Batch and real-time streaming use **different provider APIs with different `custom_params` shapes**. The parameters documented on this page apply to **batch** transcription (e.g. Gladia's [pre-recorded API](https://docs.gladia.io/api-reference/v2/pre-recorded/init)). For `streaming_config.transcription.custom_params`, use the provider's **live API** shape instead (e.g. Gladia's [live API](https://docs.gladia.io/api-reference/v2/live/init), where translation is nested under `realtime_processing`) - see [Streaming: Custom Parameters](/docs/api-v2/streaming#custom-parameters-live-vs-batch).
</Callout>

## Bring Your Own Key (BYOK)

Using your own transcription provider API key can significantly reduce token costs. When you provide your own key:

- **Token Savings**: Transcription tokens are reduced from 0.25 tokens/hour to 0.05 tokens/hour
- **Provider Billing**: You're billed directly by the transcription provider
- **Full Control**: Manage your own provider account and usage

### Requirements

BYOK transcription is available on **Pro plans and above**. Pay-as-you-go plans use the platform's transcription keys.

### Setting Up BYOK

1. **Get Your API Key**: Obtain an API key from your transcription provider (e.g., Gladia)
2. **Include in Request**: Add the API key to `transcription_config.api_key`:

```json
{
  "transcription_enabled": true,
  "transcription_config": {
    "provider": "gladia",
    "api_key": "your-gladia-api-key-here",
    "custom_params": null
  }
}
```

3. **Track Jobs**: Use `transcription_ids` in bot details and webhooks to track your provider jobs

## Custom Parameters

v2 supports advanced transcription features through custom parameters. These are provider-specific options that enhance transcription capabilities.

For complete documentation on all available custom parameters, see the [Gladia API Reference](https://docs.gladia.io/api-reference/v2/pre-recorded/init). These shapes apply to **batch** transcription only - for real-time streaming, see [Streaming: Custom Parameters](/docs/api-v2/streaming#custom-parameters-live-vs-batch).

### Available Custom Parameters

#### Summarization

Generate AI-powered meeting summaries:

```json
{
  "transcription_config": {
    "provider": "gladia",
    "custom_params": {
      "summarization": true,
      "summarization_config": {
        "type": "general"  // or "bullet_points", "concise"
      }
    }
  }
}
```

**Summary Types:**
- `general`: General meeting summary
- `bullet_points`: Bullet-point format
- `concise`: Concise summary

#### Translation

Translate transcriptions to multiple languages:

```json
{
  "transcription_config": {
    "provider": "gladia",
    "custom_params": {
      "translation": true,
      "translation_config": {
        "target_languages": ["es", "fr", "de"],
        "model": "enhanced",  // or "base"
        "match_original_utterances": true,
        "lipsync": true,
        "context_adaptation": true
      }
    }
  }
}
```

**Translation Options:**
- `target_languages`: Array of ISO 639-1 language codes (e.g., `["es", "fr"]`)
- `model`: `"base"` (default) or `"enhanced"` for better quality
- `match_original_utterances`: Match translated utterances to original timing
- `lipsync`: Enable lip-sync for video
- `context_adaptation`: Adapt translation to context

#### Language Detection

Force specific languages or enable automatic detection:

```json
{
  "transcription_config": {
    "provider": "gladia",
    "custom_params": {
      "language_config": {
        "languages": ["en", "es"],  // Force specific languages
        "detect_language": true  // Enable automatic detection
      }
    }
  }
}
```

#### Subtitles

Generate subtitles in multiple formats:

```json
{
  "transcription_config": {
    "provider": "gladia",
    "custom_params": {
      "subtitles": true,
      "subtitles_config": {
        "formats": ["srt", "vtt"],
        "minimum_duration": 0.5,
        "maximum_duration": 5,
        "maximum_characters_per_row": 42,
        "maximum_rows_per_caption": 2,
        "style": "default"  // or "compliance"
      }
    }
  }
}
```

#### Custom Vocabulary

Improve accuracy for domain-specific terms:

```json
{
  "transcription_config": {
    "provider": "gladia",
    "custom_params": {
      "custom_vocabulary": [
        "MeetingBaaS",
        "API",
        "webhook"
      ],
      "custom_vocabulary_config": {
        "vocabulary": [
          {
            "value": "MeetingBaaS",
            "intensity": 0.8,
            "pronunciations": ["meeting-baas", "meeting-bass"]
          }
        ],
        "default_intensity": 0.7
      }
    }
  }
}
```

#### Additional Features

Other available custom parameters:

- **Moderation**: Content moderation and filtering
- **Named Entity Recognition**: Extract names, organizations, locations
- **Sentiment Analysis**: Analyze sentiment of utterances
- **Chapterization**: Automatically create meeting chapters
- **Name Consistency**: Maintain consistent speaker names
- **Custom Spelling**: Custom spelling dictionary
- **Structured Data Extraction**: Extract structured data using class definitions
- **Audio to LLM**: Apply LLM prompts to transcription output
- **Punctuation Enhanced**: Enhanced punctuation accuracy

For complete documentation on all custom parameters and their configuration options, see the [Gladia API Reference](https://docs.gladia.io/api-reference/v2/pre-recorded/init). You can also check the [Meeting BaaS API Reference](/docs/api-v2/reference) for our API schema.

## Transcription Output

v2 provides two types of transcription files:

### Raw Transcription (`raw_transcription.json`)

Contains the complete, unmodified response from the transcription provider:

- **Includes**: All custom parameters (LLM summaries, translations, metadata)
- **Format**: Provider-specific structure
- **Use Case**: Access advanced features like summaries, translations, custom metadata
- **Note**: Presented as an array without time duration offsets or speaker diarization. Best used alongside `output_transcription.json`.

**Example Structure:**
```json
{
  "bot_id": "uuid",
  "transcriptions": [
    {
      "transcription": {
        "utterances": [...],
        "summary": "Meeting discussed Q4 goals...",
        "languages": ["en", "es"],
        "metadata": {...}
      }
    }
  ]
}
```

### Output Transcription (`output_transcription.json`)

Standardized format across all providers:

- **Format**: Consistent structure regardless of provider
- **Features**: 
  - Speaker diarization with participant names
  - Timestamps adjusted for multi-chunk recordings
  - Standardized utterance format
- **Use Case**: General transcription processing and display

**Example Structure:**
```json
{
  "result": {
    "utterances": [
      {
        "start": 0.5,
        "end": 2.1,
        "text": "Hello everyone",
        "speaker": "John Doe",
        "language": "en"
      }
    ]
  }
}
```

## Accessing Transcriptions

Transcriptions are available via presigned S3 URLs in:

1. **Bot Details** (`GET /v2/bots/:bot_id`): Artifacts array includes transcription URLs
2. **Webhooks** (`bot.completed`): Transcription URLs in webhook payload
3. **Callbacks**: Same URLs as webhooks

### Presigned URLs

- **Validity**: 4 hours from generation
- **Security**: Time-limited access to transcription files
- **Download**: Fetch and store transcriptions promptly

**Example Webhook Payload:**
```json
{
  "event": "bot.completed",
  "data": {
    "bot_id": "uuid",
    "raw_transcription": "https://s3.amazonaws.com/.../raw_transcription.json",
    "transcription": "https://s3.amazonaws.com/.../output_transcription.json",
    "transcription_ids": ["gladia-job-12345"],
    "transcription_provider": "gladia"
  }
}
```

## Transcription IDs

For BYOK users, `transcription_ids` provides an array of provider job IDs:

- **Purpose**: Track your own transcription jobs with the provider
- **Error Correlation**: Match transcription errors to specific provider job IDs
- **Multi-Chunk Support**: Each audio chunk gets its own provider ID
- **Available In**: Bot details and webhook payloads

**Example:**
```json
{
  "transcription_ids": ["gladia-job-12345", "gladia-job-12346"],
  "transcription_provider": "gladia"
}
```

## Token Consumption

Transcription token consumption depends on whether you use BYOK:

### With Platform Key (Default)
- **Recording**: 1 token/hour
- **Transcription**: 0.25 tokens/hour
- **Total**: ~1.25 tokens/hour

### With BYOK
- **Recording**: 1 token/hour
- **BYOK Transcription**: 0.05 tokens/hour
- **Total**: ~1.05 tokens/hour

**Note**: Custom parameters (summarization, translation, etc.) may incur additional costs from the transcription provider when using BYOK. Check your provider's pricing.

## Error Handling

If transcription fails:

- **Error Code**: `TRANSCRIPTION_FAILED` in bot status
- **Token Charging**: Recording and streaming tokens are charged, but transcription tokens are not
- **Retry**: Use the re-transcribe endpoint to retry transcription
- **Webhook**: `bot.failed` webhook includes error details

See [Error Codes](/docs/api-v2/error-codes) for complete error information.

## Best Practices

1. **Use BYOK for Cost Savings**: If you have high transcription volume, BYOK can significantly reduce costs
2. **Download Promptly**: Presigned URLs expire after 4 hours - download transcriptions quickly
3. **Store Long-Term**: If you need transcriptions long-term, download and store them in your own storage
4. **Use Raw Transcription**: Access LLM summaries, translations, and custom features from raw transcription
5. **Track Transcription IDs**: For BYOK users, use `transcription_ids` to correlate with provider jobs
6. **Handle Errors**: Implement retry logic for transcription failures

## Examples

### Basic Transcription

```bash
curl -X POST "https://api.meetingbaas.com/v2/bots" \
  -H "x-meeting-baas-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "meeting_url": "https://meet.google.com/abc-defg-hij",
    "bot_name": "Notetaker",
    "transcription_enabled": true,
    "transcription_config": {
      "provider": "gladia"
    }
  }'
```

### BYOK with Summarization

```bash
curl -X POST "https://api.meetingbaas.com/v2/bots" \
  -H "x-meeting-baas-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "meeting_url": "https://meet.google.com/abc-defg-hij",
    "bot_name": "AI Notetaker",
    "transcription_enabled": true,
    "transcription_config": {
      "provider": "gladia",
      "api_key": "your-gladia-api-key",
      "custom_params": {
        "summarization": true,
        "summarization_config": {
          "type": "bullet_points"
        }
      }
    }
  }'
```

### Multi-Language Translation

```bash
curl -X POST "https://api.meetingbaas.com/v2/bots" \
  -H "x-meeting-baas-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "meeting_url": "https://meet.google.com/abc-defg-hij",
    "bot_name": "Multilingual Notetaker",
    "transcription_enabled": true,
    "transcription_config": {
      "provider": "gladia",
      "custom_params": {
        "translation": true,
        "translation_config": {
          "target_languages": ["es", "fr", "de"],
          "model": "enhanced"
        },
        "language_config": {
          "detect_language": true
        }
      }
    }
  }'
```

## Next Steps

- Learn about [Getting the Data](/docs/api-v2/getting-started/getting-the-data) to access transcriptions
- Set up [Webhooks](/docs/api-v2/webhooks) to receive transcription URLs automatically
- Check the [API Reference](/docs/api-v2/reference) for complete parameter documentation



---

## Versioning

What an API v2 version number means, what we count as a breaking change, and how changes reach you.

### Source: ./content/docs/api-v2/versioning.mdx


The v2 API is versioned as `v2.MINOR.PATCH`. The `/v2` in the URL is the major version and is what your integration targets; it does not change between releases. The minor and patch numbers identify individual releases and are what the [release notes](/api-v2/releases) are organised by.

## What a release number means

- **Patch** (`v2.6.15` → `v2.6.16`): fixes and behaviour improvements. Nothing you have to do.
- **Minor** (`v2.5.x` → `v2.6.0`): new capabilities, such as new endpoints, fields or webhook events. Existing calls keep working.
- **Major** (`v1` → `v2`): a new API surface with its own base path. The previous major keeps running while you migrate; see the [migration guide](/docs/api-v2/migration-guide).

## Changes we consider compatible

Your integration should tolerate these without a code change; they can appear in any release:

- New endpoints, and new optional request parameters or body fields
- New fields in responses and webhook payloads
- New values in enumerations such as bot status codes, error codes and webhook event types
- New webhook events (your handler should ignore events it does not recognise)
- Changes to the order of fields in a JSON object
- More specific error messages behind an unchanged HTTP status and error code

## Changes we consider breaking

- Removing or renaming an endpoint, request parameter, response field or webhook event
- Changing a field's type, format or meaning
- Making an optional parameter required, or tightening validation so that previously accepted requests are rejected
- Changing the HTTP status or error code returned for an existing condition
- Changing authentication or rate-limiting rules in a way that rejects previously valid traffic

Every release carries a **Breaking Changes** section in its notes. When it is not "None", the entry says what changed, who is affected and what to do, and the release is flagged on the [releases timeline](/api-v2/releases).

## Deprecations

When something is going away, it is announced under **Deprecations** in the release notes of the version that deprecates it, together with the replacement to use. The deprecated behaviour keeps working until the release that removes it, which lists the removal under Breaking Changes.

## Staying informed

- The [releases page](/api-v2/releases) reads the release notes straight from GitHub, so it reflects a new version within minutes of it shipping.
- The [API reference](/docs/api-v2/reference) and the [OpenAPI specification](https://api.meetingbaas.com/v2/openapi.json) always describe the currently deployed version.
- The [TypeScript SDK](/docs/typescript-sdk) is versioned independently of the API; SDK releases follow API releases that add or change surface.


---

## Webhooks

Complete guide to webhooks in Meeting BaaS v2

### Source: ./content/docs/api-v2/webhooks.mdx


Webhooks allow you to receive real-time notifications about bot and calendar events. Instead of polling the API, you can configure webhook endpoints that will receive HTTP POST requests when events occur.

## Overview

Meeting BaaS v2 uses [SVIX](https://www.svix.com/) for webhook delivery, ensuring reliable delivery with retries and delivery status tracking.

## Webhook Configuration

Webhooks are configured at the account level. You can set up webhook endpoints in your account settings to receive notifications for all bot and calendar events.

## Callbacks vs Webhooks

Meeting BaaS v2 supports two notification mechanisms:

1. **Webhooks** (Account-level): Configured in your account settings, sent via SVIX. All events for all bots are sent to your configured webhook endpoints.

2. **Callbacks** (Bot-specific): Configured per-bot when creating a bot using the `callback_config` parameter. Callbacks are direct HTTP requests (POST or PUT) sent to your specified URL when that specific bot completes or fails.

**Key Differences:**

- **Webhooks**: Account-level, sent via SVIX with signature verification, all events
- **Callbacks**: Bot-specific, direct HTTP requests, only for bot completion/failure events

You can use both webhooks and callbacks together - they serve different purposes and complement each other.

## Webhook Security

All webhooks are signed using SVIX's signature verification. The signature is included in the `svix-id`, `svix-timestamp`, and `svix-signature` headers.

To verify webhooks, use SVIX's verification libraries or verify the signature manually using your webhook signing secret.

## Bot Webhooks

### `bot.status_change`

Triggered whenever a bot's status changes (e.g., from `queued` to `joining_call`, from `joining_call` to `in_call_recording`, etc.).

**Use Cases:**

- Track bot progress in real-time
- Update UI to show current bot state
- Trigger actions based on status changes
- Monitor bot lifecycle

**Payload:**

```json
{
  "event": "bot.status_change",
  "data": {
    "bot_id": "123e4567-e89b-12d3-a456-426614174000",
    "event_id": "789e0123-e45b-67c8-d901-234567890abc",
    "status": {
      "code": "in_call_recording",
      "created_at": "2025-01-15T10:30:00Z",
      "start_time": 1736941800
    },
    "extra": {
      "customer_id": "12345"
    }
  }
}
```

**Status object fields:**

- `code`: The status code (see list below)
- `created_at`: ISO 8601 timestamp when this status change occurred
- `start_time` *(optional)*: Unix timestamp in seconds when recording started. Only present on `in_call_recording`
- `error_message` *(optional)*: Human-readable description of the failure. Present on `recording_failed` and `meeting_error`, and may appear on other error states

**Status Codes**

The `status.code` field can contain any of the values below, grouped here by where they occur in the bot's lifecycle.

*Lifecycle*:

- `queued`: Bot is queued and waiting to join
- `pickup_delayed`: The bot has stayed in the `queued` status longer than the expected pickup window. **No action is required on your end** — this is informational. The bot may still proceed normally to `joining_call`, and our team is automatically notified to investigate persistent occurrences
- `transcribing`: Bot has exited and transcription is in progress
- `completed`: Bot has finished successfully (terminal state)
- `failed`: Bot has failed (terminal state)

*In-call progress*:

- `joining_call`: Bot is attempting to join the meeting
- `in_waiting_room`: Bot is in the meeting's waiting room / lobby
- `in_waiting_for_host`: Bot is waiting for the host to start or admit it (Zoom only)
- `in_call_not_recording`: Bot has joined the call but recording has not yet started
- `in_call_recording`: Bot is in the meeting and recording. The payload includes `start_time`
- `recording_paused`: Recording has been paused (e.g., via the pause-recording endpoint)
- `recording_resumed`: Recording has resumed after a pause
- `call_ended`: Bot has left the meeting
- `recording_succeeded`: Recording finished and artifacts were captured successfully
- `recording_failed`: Recording could not be produced. The payload includes `error_message`

*Intermediary signals* (Google Meet / Microsoft Teams) — these indicate *why* a recording is about to fail, and are followed by a `recording_failed` status and, ultimately, a `bot.failed` webhook:

- `api_request_stop`: Bot was stopped via the [leave-bot endpoint](/docs/api-v2/reference/bots/leaveBot)
- `bot_rejected`: Bot was denied entry to the meeting (e.g., host rejected the join request)
- `bot_removed`: Bot was removed from the meeting by a participant after joining
- `bot_removed_too_early`: Bot was removed before recording could start
- `waiting_room_timeout`: Bot timed out waiting to be admitted from the waiting room
- `invalid_meeting_url`: The meeting URL was invalid or could not be opened
- `meeting_error`: A general meeting-related error occurred. The payload includes `error_message`

<Callout type="info">
If you only care about terminal outcomes, watch for `completed` and `failed` (or subscribe to the `bot.completed` and `bot.failed` webhooks instead). The other codes are useful for live progress tracking but are not required reading.
</Callout>

### `bot.completed`

Triggered when a bot successfully completes recording and processing.

**Use Cases:**

- Download meeting recordings automatically
- Process transcriptions
- Trigger post-meeting workflows
- Update meeting records in your system

**Payload:**

```json
{
  "event": "bot.completed",
  "data": {
    "bot_id": "123e4567-e89b-12d3-a456-426614174000",
    "event_id": "789e0123-e45b-67c8-d901-234567890abc",
    "transcription": "https://s3.amazonaws.com/.../transcription.json",
    "mp4": "https://s3.amazonaws.com/.../video.mp4",
    "audio": "https://s3.amazonaws.com/.../audio.mp3",
    "diarization": "https://s3.amazonaws.com/.../diarization.jsonl",
    "chat_messages": "https://s3.amazonaws.com/.../chat_messages.json",
    "duration_seconds": 3600,
    "participants": [...],
    "speakers": [...],
    "extra": {
      "customer_id": "12345"
    }
  }
}
```

**Note:** All artifact URLs (transcription, mp4, audio, diarization) are presigned S3 URLs valid for 4 hours. For detailed information about each artifact type, see the [Artifacts documentation](/docs/api-v2/artifacts).

### `bot.chat_message`

Triggered in real-time when a chat message is received in the meeting. This allows you to process chat messages as they happen, without waiting for the meeting to end.

**Use Cases:**

- Build real-time chat integrations
- Respond to participant questions or commands
- Log chat messages for compliance
- Trigger workflows based on chat content

**Payload:**

```json
{
  "event": "bot.chat_message",
  "data": {
    "bot_id": "123e4567-e89b-12d3-a456-426614174000",
    "event_id": "789e0123-e45b-67c8-d901-234567890abc",
    "message_id": "spaces/XxM_aNTpzGkB/messages/1773539019302815",
    "sender_name": "John Doe",
    "sender_id": 2,
    "text": "Can we discuss the Q4 roadmap?",
    "sent_at": "2025-01-15T10:32:15Z"
  },
  "extra": {
    "customer_id": "12345"
  }
}
```

**Fields:**

- `message_id`: Unique identifier for the chat message (format varies by platform)
- `sender_name`: Display name of the message sender
- `sender_id`: Participant ID of the sender. May be `null` if the sender could not be resolved to a participant
- `text`: Text content of the chat message
- `sent_at`: ISO 8601 timestamp when this webhook was dispatched by the server (not when the message was sent in the meeting)

**Note:** The bot's own messages (sent via the [send chat message](/api-v2/reference/bots/sendChatMessage) endpoint) do not trigger this webhook — only messages from meeting participants are delivered. For a complete record of all chat messages (including bot-sent messages), use the `chat_messages` artifact available in the [bot.completed webhook](#botcompleted) and [bot details endpoint](/api-v2/reference/bots/getBotDetails). See the [Artifacts documentation](/api-v2/artifacts) for the artifact structure.

### `bot.failed`

Triggered when a bot fails to complete successfully.

**Use Cases:**

- Handle errors gracefully
- Retry bot creation if appropriate
- Log failures for analysis
- Notify users of failures

**Payload:**

```json
{
  "event": "bot.failed",
  "data": {
    "bot_id": "123e4567-e89b-12d3-a456-426614174000",
    "event_id": "789e0123-e45b-67c8-d901-234567890abc",
    "error_code": "BOT_NOT_ACCEPTED",
    "error_message": "Bot was not accepted into the meeting",
    "extra": {
      "customer_id": "12345"
    }
  }
}
```

**Common Error Codes:**

- `BOT_NOT_ACCEPTED`: Bot was not accepted into the meeting
- `TIMEOUT_WAITING_TO_START`: Meeting didn't start within the timeout period
- `INSUFFICIENT_TOKENS`: Not enough tokens to create the bot
- `DAILY_BOT_CAP_REACHED`: Daily bot creation limit reached
- `INVALID_MEETING_PLATFORM`: Could not determine meeting platform from URL
- `TRANSCRIPTION_ERROR`: Error occurred during transcription

## Calendar Webhooks

### `calendar.connection_created`

Triggered when a new calendar connection is created.

**Use Cases:**

- Confirm calendar integration success
- Initialize calendar-specific workflows
- Track calendar connections

**Payload:**

```json
{
  "event": "calendar.connection_created",
  "data": {
    "calendar_id": "123e4567-e89b-12d3-a456-426614174000",
    "platform": "google",
    "account_email": "user@example.com",
    "calendar_name": "Primary"
  }
}
```

### `calendar.connection_updated`

Triggered when a calendar connection is updated (e.g., OAuth credentials refreshed).

**Use Cases:**

- Track credential updates
- Monitor connection health
- Update connection status in your system

**Payload:**

```json
{
  "event": "calendar.connection_updated",
  "data": {
    "calendar_id": "123e4567-e89b-12d3-a456-426614174000",
    "platform": "google",
    "status": "active"
  }
}
```

### `calendar.connection_deleted`

Triggered when a calendar connection is deleted.

**Use Cases:**

- Clean up calendar-related data
- Notify users of disconnection
- Update UI to reflect removal

**Payload:**

```json
{
  "event": "calendar.connection_deleted",
  "data": {
    "calendar_id": "123e4567-e89b-12d3-a456-426614174000",
    "platform": "google"
  }
}
```

### `calendar.connection_error`

Triggered when a calendar connection encounters an error (e.g., OAuth token refresh failed).

**Use Cases:**

- Alert users to connection issues
- Trigger automatic reconnection attempts
- Log errors for troubleshooting

**Payload:**

```json
{
  "event": "calendar.connection_error",
  "data": {
    "calendar_id": "123e4567-e89b-12d3-a456-426614174000",
    "platform": "google",
    "error": "OAuth token refresh failed",
    "status": "error"
  }
}
```

### `calendar.events_synced`

Triggered after a calendar sync operation completes (initial sync).

**Use Cases:**

- Confirm sync completion
- Process newly synced events
- Update event cache

**Payload:**

```json
{
  "event": "calendar.events_synced",
  "data": {
    "calendar_id": "123e4567-e89b-12d3-a456-426614174000",
    "events_synced": 42,
    "sync_type": "full"
  }
}
```

### `calendar.event_created`

Triggered when a new event is created in a connected calendar.

**Use Cases:**

- Automatically schedule bots for new events
- Update event calendars
- Trigger event-specific workflows

**Payload:**

```json
{
  "event": "calendar.event_created",
  "data": {
    "calendar_id": "123e4567-e89b-12d3-a456-426614174000",
    "event_type": "one_off",
    "series_id": "789e0123-e45b-67c8-d901-234567890abc",
    "series_bot_scheduled": false,
    "instances": [
      {
        "event_id": "abc123...",
        "title": "Team Meeting",
        "start_time": "2025-01-20T10:00:00Z",
        "end_time": "2025-01-20T11:00:00Z",
        "meeting_url": "https://meet.google.com/...",
        "bot_scheduled": false
      }
    ]
  }
}
```

### `calendar.event_updated`

Triggered when an existing event is updated in a connected calendar.

**Use Cases:**

- Update bot schedules if meeting time changes
- Sync event changes to your system
- Handle event modifications

**Payload:**

```json
{
  "event": "calendar.event_updated",
  "data": {
    "calendar_id": "123e4567-e89b-12d3-a456-426614174000",
    "event_type": "recurring",
    "series_id": "789e0123-e45b-67c8-d901-234567890abc",
    "series_bot_scheduled": true,
    "affected_instances": [
      {
        "event_id": "abc123...",
        "title": "Team Meeting",
        "start_time": "2025-01-20T10:00:00Z",
        "end_time": "2025-01-20T11:00:00Z",
        "meeting_url": "https://meet.google.com/...",
        "bot_scheduled": true
      }
    ]
  }
}
```

### `calendar.event_cancelled`

Triggered when an event is cancelled in a connected calendar.

**Use Cases:**

- Cancel scheduled bots for cancelled events
- Update event status
- Clean up event-related data

**Payload:**

```json
{
  "event": "calendar.event_cancelled",
  "data": {
    "calendar_id": "123e4567-e89b-12d3-a456-426614174000",
    "event_type": "one_off",
    "series_id": "789e0123-e45b-67c8-d901-234567890abc",
    "series_bot_scheduled": false,
    "cancelled_instances": [
      {
        "event_id": "abc123...",
        "title": "Team Meeting",
        "start_time": "2025-01-20T10:00:00Z"
      }
    ]
  }
}
```

## Webhook Delivery

### Retry Schedule

Failed webhook deliveries are automatically retried with increasing delays:

| Attempt | Delay | Cumulative Time |
|---------|-------|-----------------|
| 1 (initial) | — | 0s |
| 2 | 5 seconds | ~5s |
| 3 | 10 seconds | ~15s |
| 4 | 5 minutes | ~5 min |
| 5 | 15 minutes | ~20 min |
| 6 | 30 minutes | ~50 min |
| 7 | 2 hours | ~3 hours |

All retries complete within approximately 3 hours. This is intentional — `bot.completed` webhooks include signed download URLs (for video, audio, transcription) that are **valid for 4 hours**. Keeping retries within this window ensures that URLs in the payload remain usable.

A small amount of random jitter (±20%) is added to each retry delay to prevent thundering herd issues.

### Timeouts and Overload Protection

- **Successful acknowledgement**: Only HTTP `2xx` responses are treated as successful delivery. Any other status code — including `3xx` redirects, `4xx` client errors, and `5xx` server errors — is considered a failure and will be retried. Failed deliveries contribute to the retry schedule and, over time, to the [auto-disable](#auto-disable) behavior.
- **Request timeout**: Your endpoint must respond within 30 seconds. Requests that exceed this limit are treated as failures and retried.
- **Overload penalty**: If your endpoint returns a `429 Too Many Requests` status or times out, a minimum 60-second delay is applied before the next retry, regardless of the scheduled delay.

### Idempotency and Ordering

- **Idempotency**: Webhooks may be delivered multiple times — ensure your handler is idempotent
- **Ordering**: Webhooks are generally delivered in order, but network issues or retries may cause out-of-order delivery

### Endpoint Auto-Disable <span id="auto-disable" />

If your webhook endpoint fails continuously for an extended period, it will be automatically disabled to prevent unnecessary retry traffic:

1. **After all retry attempts fail** for a message, a `webhook_delivery_exhausted` operational alert is emitted (if you have [alert rules](/docs/api-v2/alerts) configured for this type)
2. **After 5 days of continuous failure** (no successful delivery across any message), the endpoint is automatically disabled
3. **When auto-disabled**, the team owner receives an email notification with the endpoint details and common failure causes
4. **Your endpoint status** is updated in the dashboard — you'll see it marked as disabled
5. **No new deliveries** are attempted to a disabled endpoint until you manually re-enable it from the dashboard

<Callout type="warn">
Once an endpoint is auto-disabled, all subsequent webhook messages are silently dropped. Configure a **Webhook Delivery Exhausted** alert rule to get notified per-message as retries fail, well before the 5-day auto-disable threshold. See [Alerts](/docs/api-v2/alerts) for setup instructions.
</Callout>

A single successful delivery at any point resets the failure clock — the 5-day timer only applies to endpoints that fail every delivery attempt without any success.

## Testing Webhooks

You can test your webhook endpoint using tools like:

- [ngrok](https://ngrok.com/) for local development
- [webhook.site](https://webhook.site/) or [webhook.cool](https://webhook.cool) for testing
- [SVIX CLI](https://www.svix.com/docs/cli/) for local testing

## Callbacks

Callbacks are bot-specific HTTP requests sent directly to a URL you provide when creating a bot. Unlike webhooks (which are account-level and sent via SVIX), callbacks are:

- **Bot-specific**: Configured per-bot using `callback_config` when creating a bot
- **Direct HTTP**: Sent directly to your URL (not via SVIX)
- **Limited events**: Only sent for `bot.completed` and `bot.failed` events
- **Same payload**: Uses the same payload structure as webhooks

### Configuring Callbacks

When creating a bot, include the `callback_config` in your request:

```json
{
  "bot_name": "My Bot",
  "meeting_url": "https://meet.google.com/...",
  "callback_enabled": true,
  "callback_config": {
    "url": "https://your-server.com/webhook",
    "method": "POST",
    "secret": "your-secret-key"
  }
}
```

### Callback Security

If you provide a `secret` in `callback_config`, it will be included in the `x-mb-secret` header of all callback requests. Use this to verify that callbacks are coming from Meeting BaaS.

### Callback Delivery and Retries

Callbacks are delivered with automatic retries on failure:

| Attempt | Delay |
|---------|-------|
| 1 (initial) | — |
| 2 | 1 second |
| 3 | 5 seconds |
| 4 | 15 seconds |

- Only retries on **network errors** and **5xx server responses**
- Does **not** retry on **4xx client errors** (e.g., 400, 401, 403, 404)
- 30-second request timeout per attempt

If all 4 attempts fail, you can manually retry using the `POST /v2/bots/:bot_id/retry-callback` endpoint. This generates a fresh payload with new signed download URLs.

## Resending Webhooks

If a webhook delivery fails, you can resend it using the `POST /v2/bots/:bot_id/resend-webhook` endpoint.


---

## Bring Your Own Storage

Connect your own S3-compatible object storage to Meeting BaaS

### Source: ./content/docs/bring-your-own-storage/index.mdx


## Overview

Bring Your Own Storage lets you point Meeting BaaS at S3-compatible object storage
that **you own**. Meeting artifacts (recordings, transcripts, logs) are written
directly into your buckets with your credentials instead of landing on
Meeting BaaS infrastructure.

It's opt-in and additive — teams without a configuration keep using default
storage with zero change.

## What you need

- **An S3-compatible endpoint** (AWS S3, Scaleway Object Storage, MinIO, Ceph, etc.)
- **Three buckets** (they can be the same bucket — Meeting BaaS prefixes keys by bot UUID):
  - Artifacts bucket (video, audio, screenshots)
  - Audio chunks bucket
  - Logs bucket
- **Two access keys** on those buckets:

| Key | Permissions | Used by |
|-----|-------------|---------|
| **Ingest** | `PutObject`, `PutObjectTagging`, `AbortMultipartUpload` | Bots only (upload recordings) |
| **Service** | `GetObject`, `ListBucket`, `PutObject`, `DeleteObject` | API server (serve artifacts, write transcripts, delete on retention) |

<Callout type="warn">
  The ingest key is the only credential that leaves our infrastructure — it rides
  alongside the bot into meetings. That's why it is **scoped to upload only**:
  a compromised pod can add objects but cannot read, list, or delete your recordings.
</Callout>

## Configuring it

### Dashboard

1. Go to **Settings → Storage** (`/settings/storage`).
2. Fill in your endpoint, region, bucket names, and both key pairs.
3. Toggle **force path style** if your provider requires it (needed by MinIO, Ceph,
   and most self-hosted gateways; not needed for AWS or Scaleway).
4. Click **Save**. Meeting BaaS will verify your credentials by writing a test
   object — you'll see the result immediately.

### API

| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | [`/v2/storage-config`](/docs/api-v2/reference/storage/getStorageConfig) | Get current configuration (404 if unset) |
| `PUT` | [`/v2/storage-config`](/docs/api-v2/reference/storage/setStorageConfig) | Set or replace configuration |
| `POST` | [`/v2/storage-config/test`](/docs/api-v2/reference/storage/testStorageConfig) | Re-run the access check |
| `DELETE` | [`/v2/storage-config`](/docs/api-v2/reference/storage/deleteStorageConfig) | Disable — new bots go back to Meeting BaaS storage. Nothing is deleted. |

The same endpoints are available at `/bff/storage-config` for dashboard auth.

<Callout type="info">
  Neither secret access key is ever returned by the API. Replacing a configuration
  always requires re-entering both key pairs.
</Callout>

## How it works

- **A bot always resolves to the storage it was written to**, regardless of your
  current configuration. Changing or removing your storage config only affects
  **future** bots — existing recordings stay where they are.
- **Disabling** (via `DELETE`) makes new bots use Meeting BaaS storage again.
  Nothing is deleted from your buckets.
- **Revoking our access** on your side is what makes older artifacts unreadable
  — not the delete operation.

## Data residency

Bots writing to your storage **skip the EFS fallback** by default. If an upload fails
after retries, the artifact is reported as failed rather than copied onto
Meeting BaaS infrastructure. This preserves the data residency guarantee.

If you prefer durability over residency (e.g., you chose your own bucket for cost
reasons), you can enable **allow transient spill** in the dashboard. A warning will
confirm you understand the tradeoff.

## Bucket requirements

- Buckets should be **private** — artifacts are served through short-lived signed URLs.
- Buckets must be **reachable from the public internet** — transcription providers
  fetch audio directly from signed URLs.
- Buckets must allow **cross-origin reads from the dashboard** — see below.

### CORS

The dashboard does not hand your browser a plain download link. It fetches the
object and rebuilds it locally, and it reads transcript JSON the same way. Both
are cross-origin reads against *your* endpoint, so without a CORS rule the
browser discards the response before the dashboard ever sees it.

Symptom: artifacts are listed on the bot page but clicking one reports
`Failed to download file`, and transcripts never render. The browser console
shows `No 'Access-Control-Allow-Origin' header is present on the requested
resource`. The signed URL itself is fine — paste it into a new tab and the file
downloads, because top-level navigation is not subject to CORS.

The rule your **artifacts bucket** needs, whichever provider you are on:

| | Value |
|---|---|
| Allowed origins | `https://dashboard.meetingbaas.com` |
| Allowed methods | `GET`, `HEAD` |
| Allowed headers | `*` |
| Exposed headers | `Content-Length`, `Content-Type` |
| Max age | `3000` |

<Callout type="warn">
  Two different URLs are involved and they are easy to swap. The **allowed
  origin** is the Meeting BaaS dashboard — that is who is asking to read. The
  **endpoint** in the commands below is your own storage provider's S3 API —
  that is where the bucket lives and where the policy is stored. Neither is ever
  the other.
</Callout>

CORS is part of the S3 API (`PutBucketCors`), so every S3-compatible provider
supports it; only the way you reach it differs.

```json title="cors.json"
{
  "CORSRules": [
    {
      "AllowedOrigins": ["https://dashboard.meetingbaas.com"],
      "AllowedMethods": ["GET", "HEAD"],
      "AllowedHeaders": ["*"],
      "ExposeHeaders": ["Content-Length", "Content-Type"],
      "MaxAgeSeconds": 3000
    }
  ]
}
```

```bash title="aws-cli — works against any S3-compatible endpoint"
aws s3api put-bucket-cors \
  --endpoint-url https://<your-storage-endpoint> \
  --bucket <artifacts-bucket> \
  --cors-configuration file://cors.json
```

Other routes to the same call:

- **s3cmd** — `s3cmd setcors cors.xml s3://<artifacts-bucket>`, using the XML
  form of the same rule rather than JSON.
- **MinIO client** — `mc` against your alias.
- **Provider console** — AWS S3 and Cloudflare R2 expose CORS in their bucket
  UI. Scaleway does not, at time of writing; use the API or a CLI there.
- **Terraform** — a `cors_rule` block on your bucket resource, if the bucket is
  managed as code.

<Callout type="warn">
  `PutBucketCors` is a bucket-*configuration* call, one tier above the object
  read/write/delete your Service key holds — that key will return `Forbidden`,
  and the scoping is deliberate. Run it with an owner-level credential: on
  Scaleway, an API key whose application has `ObjectStorageFullAccess` on the
  project holding the bucket; on AWS, a principal with `s3:PutBucketCORS`. Note
  that it **replaces** the bucket's entire CORS policy rather than appending
  to it.
</Callout>

<Callout type="info">
  The storage access check does not cover this. It runs server-side, where CORS
  does not apply, so a configuration can pass verification and still leave the
  dashboard unable to display anything.
</Callout>


---

## Community & Support

Join our Discord Community, or ping us on our socials.

### Source: ./content/docs/api/community-and-support.mdx


## Community:

- [Join our Discord](https://discord.com/invite/dsvFgDTr6c)
- [Star us on Github](https://github.com/Meeting-Baas/Meeting-Bot-As-A-Service)

## Contact & Support

Planning to use more than 100 hours a month?  
Expect a response within the day.

- Twitter
- <ContactLink />
- Slack and Teams channels for customers and partners.


---

## Introduction

Get started with the Meeting BaaS API

### Source: ./content/docs/api/index.mdx


<Callout type="info">
  We provide optimized documentation for both LLMs and recent MCP server updates. For more on our LLM integration, 
  see [LLMs](/llms/api) and for MCP access, visit [auth.meetingbaas.com](https://auth.meetingbaas.com/home).
</Callout>

**Meeting BaaS** 🐟 provides _Meetings Bots As A Service_, with integrated transcription.

This allows you to:

1. **interact with**
2. **transcribe**
3. **AI summarize**

video-meetings through a single unified API. Using Meeting BaaS, you can deploy bots on Microsoft Teams, Google Meet, and Zoom in less than 1 minute.

Our meeting bots act as regular meeting participants with full audio and visual capabilities.

They can listen, speak, use chat, and appear with customizable names and profile pictures.

Just provide a meeting URL through a simple command, and meeting bots will connect to the meeting, give their name and ask to be let in.

Once inside, they record the meeting until it ends, and provide you with the data as they go.


---

## Introduction

Get started with Model Context Protocol servers for Meeting BaaS

### Source: ./content/docs/mcp-servers/index.mdx


<Callout type="info">
  We provide optimized documentation for both LLMs and recent MCP server updates. For more on our LLM integration, 
  see [LLMs](/llms/mcp-servers) and for MCP access, visit [auth.meetingbaas.com](https://auth.meetingbaas.com/home).
</Callout>

## What is Model Context Protocol?

Model Context Protocol (MCP) is a standard that lets AI assistants like Claude connect with other services. For Meeting BaaS, an MCP server helps with:

- Meeting transcripts and analysis
- Meeting scheduling
- AI assistance during meetings
- Secure connections
- Enterprise-ready infrastructure

  Learn more about Model Context Protocol in [Anthropic's technical overview](https://www.anthropic.com/news/model-context-protocol).
   
## Deployment Options

Meeting BaaS offers two robust MCP server implementations to match your specific needs:

<Cards>
  <Card
    title="MCP on Vercel"
    icon={<Cloud className="text-blue-400" />}
    href="/docs/mcp-servers/mcp/vercel-mcp"
  >
    A serverless solution optimized for Vercel deployment, offering:
    - Zero infrastructure management
    - Automatic scaling
    - Global edge deployment
    - Simplified CI/CD integration
  </Card>
  <Card
    title="Meeting MCP"
    icon={<Server className="text-green-400" />}
    href="/docs/mcp-servers/mcp/meeting-mcp"
  >
    A self-hosted solution providing:
    - Complete infrastructure control
    - Custom deployment options
    - Enhanced security configurations
    - Local development flexibility
  </Card>
</Cards>

## Key Capabilities

Both MCP servers provide access to Meeting BaaS capabilities through standardized tools:

<Accordions>
  <Accordion title="Meeting Management" icon={<VideoIcon />} defaultOpen>
    - Create and invite meeting bots to video conferences
    - Record and transcribe meetings automatically
    - Manage speaking bots with different personas
    - Configure recording settings and bot behavior
  </Accordion>

  <Accordion title="Calendar Integration" icon={<Calendar />}>
    - Connect Google and Microsoft calendars
    - Schedule automated recordings of upcoming meetings
    - Manage calendar events and recordings
    - Receive guidance on OAuth setup and configuration
  </Accordion>

  <Accordion title="Transcript & Data Access" icon={<FileText />}>
    - Search through meeting transcripts
    - Identify and share key moments from meetings
    - Generate shareable links to specific meeting segments
    - Access comprehensive meeting data and metadata
  </Accordion>
</Accordions>

## Getting Started

### Prerequisites

The following requirements must be met before setting up an MCP server:

<Cards>
  <Card title="Development Tools" icon={<Terminal />}>
    - Node.js v16.x or later
    - npm or yarn package manager 
    - Git for version control
    - Docker for local development
    - VS Code or your preferred IDE
  </Card>

  <Card title="Account Access" icon={<Key />}>
    - Meeting BaaS account
    - Valid API credentials
    - Access to deployment platform
  </Card>
</Cards>

### Setup Instructions

Follow these steps to get your MCP server up and running:

<Steps>
  <Step title="Create Account & Get API Access">
    <Card>
      1. Sign up at [meetingbaas.com](https://meetingbaas.com)
      2. Navigate to API section in dashboard
      3. Generate new API key
      4. Store credentials securely
    </Card>
  </Step>

  <Step title="Configure Development Environment">
    Choose your deployment type and set up the codebase:

    ```bash
    # Clone repository
    git clone https://github.com/meetingbaas/mcp-vercel     # For Vercel
    # OR
    git clone https://github.com/meetingbaas/meeting-mcp    # For self-hosted

    # Install and configure
    cd <repository-name>
    npm install
    cp .env.example .env
    ```
  </Step>

  <Step title="Deploy Your Server">
    <Cards>
      <Card
        title="Cloud Deployment"
        icon={<Cloud />}
        href="https://github.com/Meeting-Baas/mcp-on-vercel"
      >
        Deploy to Vercel for a managed cloud solution with automatic scaling
      </Card>
      
      <Card
        title="Self-Hosted Setup"
        icon={<Server />}
        href="https://github.com/Meeting-Baas/meeting-mcp"
      >
        Deploy locally or to your own infrastructure for maximum control
      </Card>
    </Cards>
  </Step>
</Steps>

For extended functionality, both deployment options fully support the [Meeting BaaS TypeScript SDK](/docs/typescript-sdk).


---

## Configuration

Configure feature flags and environment variables for your deployment

### Source: ./content/docs/self-hosting/configuration.mdx


This guide explains how to configure Meeting BaaS v2 for your self-hosted deployment using feature flags and environment variables.

## Feature Flags Overview

Feature flags control which functionality is enabled in your deployment. All flags default to `false`, making minimal deployments straightforward.

### Feature Flag Reference

| Flag | Default | Description | Required Services |
|------|---------|-------------|-------------------|
| `SELF_HOSTED` | `false` | Enable self-hosted mode (simplified configuration) | - |
| `ENABLE_STRIPE` | `false` | Enable Stripe billing, subscriptions, and token system | Stripe account |
| `ENABLE_SVIX` | `false` | Enable SVIX managed webhooks (otherwise use direct callbacks) | SVIX instance |
| `ENABLE_CALENDAR` | `false` | Enable Google/Microsoft calendar integration | OAuth credentials |
| `ENABLE_MULTI_TENANT` | `false` | Enable multi-tenant mode (teams, multiple users) | - |
| `ENABLE_DASHBOARD` | `false` | Enable frontend dashboard (BFF and internal routes) | OAuth credentials |
| `ENABLE_TRANSCRIPTION` | `false` | Enable Gladia transcription service | Gladia API key |
| `ENABLE_EMAIL` | `false` | Enable Resend email notifications | Resend API key |

## Configuration Files

Feature flags are configured in your environment override files:

- `environment-overrides/api_server_v2_chart/prod.yaml` - API server configuration
- `environment-overrides/job_v2_chart/prod.yaml` - Background jobs configuration

## Deployment Modes

### Single-Tenant Self-Hosted (Minimal)

Perfect for organizations that need a single team with unlimited usage:

```yaml
# environment-overrides/api_server_v2_chart/prod.yaml
featureFlags:
  selfHosted: true
  enableStripe: false
  enableSvix: false
  enableCalendar: false
  enableMultitenant: false
  enableDashboard: false
  enableTranscription: false
  enableEmail: false

selfHosted:
  staticApiKey: "your-secure-api-key-here"
  staticTeamId: "your-team-id"
```

**What's Enabled**:
- Bot creation via `/v2/bots` endpoint
- Scheduled bots via `/v2/bots/scheduled` endpoint
- Direct webhook callbacks (via `callback_url` in bot config)
- Unlimited bot usage (no token billing)
- Recording and video artifacts

**What's Disabled**:
- SVIX managed webhooks
- Stripe billing and token system
- Calendar integration
- Frontend dashboard
- Email notifications
- Multi-tenant features (team creation, deletion, invitations)
- Team deletion background job

### Single-Tenant with Dashboard

For organizations that want the frontend dashboard without multi-tenancy:

```yaml
featureFlags:
  selfHosted: true
  enableMultitenant: false
  enableDashboard: true  # Enable dashboard
  enableStripe: false
  enableSvix: false
  enableCalendar: false
  enableTranscription: false
  enableEmail: false

selfHosted:
  staticApiKey: "your-secure-api-key-here"
  staticTeamId: "your-team-id"

dashboard:
  masterAdminEmail: "admin@yourcompany.com"  # User to auto-promote to admin
```

**Additional Requirements**:
- OAuth credentials (Google and/or GitHub)
- `BETTER_AUTH_SECRET` (authentication secret)

### Multi-Tenant Self-Hosted

For organizations that need multiple teams and user management:

```yaml
featureFlags:
  selfHosted: true
  enableMultitenant: true  # Enable multi-tenant
  enableDashboard: true    # Usually enabled with multi-tenant
  enableStripe: false     # Optional: enable for billing
  enableSvix: false
  enableCalendar: false
  enableTranscription: false
  enableEmail: false
```

**Note**: When `ENABLE_MULTI_TENANT=true`, you don't need `STATIC_API_KEY` or `STATIC_TEAM_ID`. Teams are created dynamically.

## Environment Variables

### Required Variables

These are always required, regardless of feature flags:

```yaml
secret:
  # Database
  database_url: "postgres://user:password@host:port/database"
  
  # Redis
  redis_url: "redis://user:password@host:port"
  
  # SQS Queues
  sqs_queue_url_zoom: "https://sqs.region.amazonaws.com/account/queue-name"
  sqs_queue_url_meet_teams: "https://sqs.region.amazonaws.com/account/queue-name"
  aws_access_key_id_sqs: "your-sqs-access-key"
  aws_secret_access_key_sqs: "your-sqs-secret-key"
  
  # S3 Storage
  aws_access_key_id: "your-s3-access-key"
  aws_secret_access_key: "your-s3-secret-key"
  
  # Bot encryption (for transcription API keys)
  bot_encryption_secret: "generate-a-random-32-byte-key"
```

### Optional Variables

These are only needed when specific features are enabled:

#### When ENABLE_DASHBOARD=true

```yaml
secret:
  better_auth_secret: "generate-a-random-secret"  # Required for dashboard
  google_id: "your-google-oauth-client-id"
  google_secret: "your-google-oauth-client-secret"
  github_id: "your-github-oauth-client-id"
  github_secret: "your-github-oauth-client-secret"

configmap:
  frontend_baseurl: "https://dashboard.yourcompany.com"
  trusted_origins: "https://yourcompany.com,https://dashboard.yourcompany.com"
```

#### When ENABLE_CALENDAR=true

```yaml
secret:
  calendar_credentials_key: "generate-a-random-32-byte-key"  # For encrypting OAuth tokens
  google_id: "your-google-oauth-client-id"
  google_secret: "your-google-oauth-client-secret"
  # Microsoft OAuth configured via Azure AD

configmap:
  api_server_baseurl: "https://api.yourcompany.com"  # Used for webhook URLs
```

#### When ENABLE_TRANSCRIPTION=true

```yaml
secret:
  gladia_api_key: "your-gladia-api-key"

configmap:
  aws_s3_audio_chunks_bucket: "your-audio-chunks-bucket"
```

#### When ENABLE_EMAIL=true

```yaml
secret:
  resend_api_key: "your-resend-api-key"

configmap:
  resend_email_from: "Meeting BaaS <noreply@yourcompany.com>"
  support_email: "support@yourcompany.com"
```

#### When ENABLE_SVIX=true

```yaml
secret:
  svix_jwt_secret: "your-svix-jwt-secret"

configmap:
  svix_url: "http://svix.services.svc.cluster.local:8071"  # Or external URL
```

#### When ENABLE_STRIPE=true

```yaml
secret:
  stripe_secret_key: "sk_live_..."
  stripe_webhook_secret: "whsec_..."

configmap:
  stripe_pro_subscription_product_id: "prod_..."
  stripe_pro_subscription_price_id: "price_..."
  # ... other Stripe product/price IDs
```

### ConfigMap Variables

Common configuration values:

```yaml
configmap:
  # Server configuration
  port: "3001"
  host: "0.0.0.0"
  node_env: "production"
  environ: "prod"
  log_level: "info"  # or "debug" for troubleshooting
  
  # API URLs
  api_server_baseurl: "https://api.yourcompany.com"
  frontend_baseurl: "https://dashboard.yourcompany.com"  # If dashboard enabled
  domain: ".yourcompany.com"
  trusted_origins: "https://yourcompany.com,https://api.yourcompany.com"
  
  # AWS/S3 Configuration
  aws_region: "us-east-1"
  aws_default_region: "us-east-1"
  aws_endpoint_url: "https://s3.us-east-1.amazonaws.com"
  aws_endpoint_url_sqs: "https://sqs.us-east-1.amazonaws.com"
  
  # S3 Buckets
  aws_s3_artifacts_bucket: "your-company-meeting-baas-artifacts"
  aws_s3_logs_bucket: "your-company-meeting-baas-logs"
  aws_s3_audio_chunks_bucket: "your-company-meeting-baas-audio-chunks"  # If transcription enabled
  aws_s3_logo_bucket: "your-company-meeting-baas-logo"  # Optional
  aws_s3_support_bucket: "your-company-meeting-baas-support"  # Optional
```

## Background Jobs Configuration

Background jobs are configured in `environment-overrides/job_v2_chart/prod.yaml`:

```yaml
featureFlags:
  enableCalendar: false      # Must match api_server_v2_chart
  enableMultitenant: false   # Must match api_server_v2_chart
  enableTranscription: false # Must match api_server_v2_chart
  enableEmail: false         # Must match api_server_v2_chart
```

**Important**: Job chart feature flags should match the API server chart flags.

### Jobs That Run

Based on feature flags:

**Always Running**:
- `scheduled-bot-job` - Creates bots for scheduled meetings (every minute)
- `data-retention-deletion-job` - Deletes old bot data (daily)

**When ENABLE_CALENDAR=true**:
- `calendar-bot-job` - Creates bots for calendar events (every minute)
- `microsoft-calendar-resubscription-job` - Renews Microsoft subscriptions (every 6 hours)
- `google-calendar-resubscription-job` - Renews Google subscriptions (every 6 hours)
- `calendar-materialization-job` - Materializes calendar events (daily)

**When ENABLE_MULTI_TENANT=true**:
- `team-permanent-deletion-job` - Deletes soft-deleted teams (daily at 9 AM UTC)

## Generating Secrets

### Bot Encryption Secret

```bash
# Generate 32-byte key (base64 encoded)
openssl rand -base64 32
```

### Calendar Credentials Key

```bash
# Generate 32-byte key (base64 encoded)
openssl rand -base64 32
```

### Better Auth Secret

```bash
# Generate random secret
openssl rand -hex 32
```

### Static API Key

For single-tenant mode, generate a secure API key:

```bash
# Generate random key (64 characters)
openssl rand -hex 32
```

Use this as your `STATIC_API_KEY` value.

## Configuration Checklist

Before deploying, verify:

### API Server Chart

- [ ] Feature flags set correctly
- [ ] `staticApiKey` set (if single-tenant)
- [ ] `staticTeamId` set (if single-tenant)
- [ ] `masterAdminEmail` set (if dashboard enabled)
- [ ] All required secrets filled in
- [ ] ConfigMap values updated (domain, URLs, etc.)
- [ ] Node selector matches your pool name
- [ ] Image repository points to your registry

### Job Chart

- [ ] Feature flags match API server chart
- [ ] Database URL set
- [ ] Required secrets filled in (based on enabled features)
- [ ] Node selector matches your pool name
- [ ] Image repository points to your registry

### Bot Charts

- [ ] Node selector matches bots pool name
- [ ] SQS queue URLs set
- [ ] S3 bucket names set
- [ ] Image repository points to your registry

### Video Device Plugin

- [ ] Node selector matches bots pool name
- [ ] Image repository points to your registry

## Environment-Specific Configuration

### Development/Staging

```yaml
configmap:
  log_level: "debug"
  environ: "dev"
  api_server_baseurl: "https://api-dev.yourcompany.com"
```

### Production

```yaml
configmap:
  log_level: "info"
  environ: "prod"
  api_server_baseurl: "https://api.yourcompany.com"
```

## Next Steps

- [Deployment](/docs/self-hosting/deployment) - Deploy the platform using the provided scripts
- [Upgrades](/docs/self-hosting/upgrades) - Learn how to upgrade your deployment


---

## Deployment

Deploy Meeting BaaS v2 to your Kubernetes cluster

### Source: ./content/docs/self-hosting/deployment.mdx


This guide walks you through deploying Meeting BaaS v2 to your Kubernetes cluster using the provided Helm charts and deployment scripts.

## Pre-Deployment Checklist

Before deploying, ensure:

- [ ] Infrastructure is set up (Kubernetes, database, Redis, S3, SQS)
- [ ] Repository structure is created with Helm charts submodule
- [ ] Environment override files are configured
- [ ] All secrets and config values are filled in
- [ ] DNS records point to your ingress IP
- [ ] Kubernetes cluster is accessible via `kubectl`
- [ ] Container images are accessible (either from Meeting BaaS registry or your own)

## Step 1: Apply Kubernetes Resources

First, apply the certificate issuer and certificate:

```bash
# Apply cluster issuer
kubectl apply -f k8s-resources/prod-certs/cluster-issuer.yaml

# Apply certificate
kubectl apply -f k8s-resources/prod-certs/cluster-certificate.yaml

# Verify certificate is issued (may take a few minutes)
kubectl get certificate -n services
```

## Step 2: Pull and Push Images (If Using Your Own Registry)

If you're mirroring images to your own registry:

```bash
# Set image tag (provided by Meeting BaaS)
export IMAGE_TAG=2026-01-27abc123def456...

# Login to Meeting BaaS registry (credentials provided)
docker login rg.fr-par.scw.cloud

# Pull images
docker pull rg.fr-par.scw.cloud/meeting-baas-prod-api-server/api-server-v2:$IMAGE_TAG
docker pull rg.fr-par.scw.cloud/meeting-baas-prod-bots/zoom-bots-v2:$IMAGE_TAG
docker pull rg.fr-par.scw.cloud/meeting-baas-prod-bots/meet-teams-bots-v2:$IMAGE_TAG
docker pull rg.fr-par.scw.cloud/baas-bots-preprod/video-device-plugin:1.0.0

# Tag for your registry
docker tag rg.fr-par.scw.cloud/meeting-baas-prod-api-server/api-server-v2:$IMAGE_TAG \
  YOUR_REGISTRY/api-server-v2:$IMAGE_TAG
docker tag rg.fr-par.scw.cloud/meeting-baas-prod-bots/zoom-bots-v2:$IMAGE_TAG \
  YOUR_REGISTRY/zoom-bots-v2:$IMAGE_TAG
docker tag rg.fr-par.scw.cloud/meeting-baas-prod-bots/meet-teams-bots-v2:$IMAGE_TAG \
  YOUR_REGISTRY/meet-teams-bots-v2:$IMAGE_TAG
docker tag rg.fr-par.scw.cloud/baas-bots-preprod/video-device-plugin:1.0.0 \
  YOUR_REGISTRY/video-device-plugin:1.0.0

# Login to your registry
docker login YOUR_REGISTRY

# Push images
docker push YOUR_REGISTRY/api-server-v2:$IMAGE_TAG
docker push YOUR_REGISTRY/zoom-bots-v2:$IMAGE_TAG
docker push YOUR_REGISTRY/meet-teams-bots-v2:$IMAGE_TAG
docker push YOUR_REGISTRY/video-device-plugin:1.0.0
```

**Note**: If you have direct access to Meeting BaaS registry, you can skip this step and use the images directly.

## Step 3: Set Up Deployment Script

Make the deployment script executable:

```bash
chmod +x helm-charts/baas_controller.sh
```

Configure your environment:

```bash
# Set environment (prod or preprod)
export ENVIRON=prod

# Set image tag
export IMAGE_TAG=2026-01-27abc123def456...
```

## Step 4: Deploy Video Device Plugin

The video device plugin must be deployed first as it provides the video device resources that bot pods require:

```bash
cd helm-charts

# Install video device plugin
export ENVIRON=prod
export IMAGE_TAG=1.0.0  # Video device plugin uses fixed version
./baas_controller.sh video-device-plugin install
```

**Verify**:

```bash
kubectl get daemonset -n services video-device-plugin
kubectl get nodes -o jsonpath='{.items[*].status.allocatable.meeting-baas\.io/video-devices}'
```

You should see video devices available on bot pool nodes.

## Step 5: Deploy API Server

Deploy the API server (this also sets up the database schema via migration job):

```bash
cd helm-charts

export ENVIRON=prod
export IMAGE_TAG=2026-01-27abc123def456...

# Install API server
./baas_controller.sh api-v2 install
```

**What Happens**:

1. Helm creates a migration Job (pre-install hook)
2. Migration Job runs database migrations
3. Migration Job completes successfully
4. Helm creates the API server Deployment
5. API server starts and runs bootstrap operations:
   - Creates self-hosted team (if `SELF_HOSTED=true` and `ENABLE_MULTI_TENANT=false`)
   - Promotes master admin (if `ENABLE_DASHBOARD=true` and `MASTER_ADMIN_EMAIL` set)
   - Syncs event types to SVIX (if `ENABLE_SVIX=true`)

**Verify**:

```bash
# Check migration job
kubectl get jobs -n services | grep migration

# Check API server pods
kubectl get pods -n services -l app.kubernetes.io/instance=api-server-v2

# Check API server logs
kubectl logs -n services -l app.kubernetes.io/instance=api-server-v2 --tail=50

# Test health endpoint
curl https://api.yourcompany.com/health
```

## Step 6: Deploy Background Jobs

Deploy the background jobs (CronJobs):

```bash
cd helm-charts

export ENVIRON=prod
export IMAGE_TAG=2026-01-27abc123def456...  # Same as API server

# Install jobs
./baas_controller.sh job-v2 install
```

**Verify**:

```bash
# Check CronJobs
kubectl get cronjobs -n services

# Check which jobs are created (based on feature flags)
kubectl get cronjobs -n services -o name

# Should see:
# - scheduled-bot-job (always)
# - data-retention-deletion-job (always)
# - calendar-bot-job (if ENABLE_CALENDAR=true)
# - team-permanent-deletion-job (if ENABLE_MULTI_TENANT=true)
# etc.
```

## Step 7: Deploy Bot Services

Deploy the bot services (they scale to zero initially):

```bash
cd helm-charts

export ENVIRON=prod
export IMAGE_TAG=2026-01-27abc123def456...  # Same as API server

# Install Zoom bots
./baas_controller.sh zoom-bots-v2 install

# Install Meet/Teams bots
./baas_controller.sh meet-teams-bots-v2 install
```

**Verify**:

```bash
# Check ScaledJobs (KEDA)
kubectl get scaledjobs -n services

# Check bot pods (should be 0 initially)
kubectl get pods -n services -l app.kubernetes.io/instance=zoom-bots-v2
kubectl get pods -n services -l app.kubernetes.io/instance=meet-teams-bots-v2
```

## Step 8: Verify Deployment

### Check All Services

```bash
# List all deployments
kubectl get deployments -n services

# List all CronJobs
kubectl get cronjobs -n services

# List all ScaledJobs
kubectl get scaledjobs -n services

# List DaemonSets
kubectl get daemonsets -n services
```

### Test API Endpoints

```bash
# Health check
curl https://api.yourcompany.com/health

# Feature flags status
curl https://api.yourcompany.com/status/features

# Configuration endpoint (if dashboard enabled)
curl https://api.yourcompany.com/v2-internal/configuration
```

### Check Logs

```bash
# API server logs
kubectl logs -n services -l app.kubernetes.io/instance=api-server-v2 --tail=100

# Check for bootstrap messages
kubectl logs -n services -l app.kubernetes.io/instance=api-server-v2 | grep bootstrap

# Check for errors
kubectl logs -n services -l app.kubernetes.io/instance=api-server-v2 | grep -i error
```

### Test Bot Creation

```bash
# Test creating a bot (single-tenant mode)
curl -X POST https://api.yourcompany.com/v2/bots \
  -H "Content-Type: application/json" \
  -H "x-meeting-baas-api-key: YOUR_STATIC_API_KEY" \
  -d '{
    "meeting_url": "https://meet.google.com/abc-defg-hij",
    "bot_name": "Test Bot"
  }'
```

**Expected Response**:

```json
{
  "success": true,
  "data": {
    "bot_id": "...",
    "status": "queued",
    ...
  }
}
```

## Step 9: Monitor Initial Jobs

Watch the first scheduled bot job run:

```bash
# Watch scheduled bot job
kubectl get jobs -n services -w | grep scheduled-bot-job

# Check job logs
kubectl logs -n services -l cron=scheduled-bot-job --tail=50
```

## Common Deployment Issues

### Migration Job Failed

```bash
# Check migration job logs
kubectl logs -n services -l app.kubernetes.io/component=migration --tail=100

# Common causes:
# - Database connection issues
# - Insufficient database permissions
# - Network connectivity problems
```

### API Server Not Starting

```bash
# Check pod status
kubectl describe pod -n services -l app.kubernetes.io/instance=api-server-v2

# Check logs
kubectl logs -n services -l app.kubernetes.io/instance=api-server-v2 --tail=100

# Common causes:
# - Missing secrets
# - Invalid configuration values
# - Database connection issues
```

### Ingress Not Working

```bash
# Check ingress
kubectl get ingress -n services

# Check ingress controller
kubectl get pods -n ingress-nginx

# Check certificate status
kubectl describe certificate -n services api-meeting-baas-tls
```

## Post-Deployment

### Enable Auto-Scaling (Optional)

If you want the API server to auto-scale:

```yaml
# environment-overrides/api_server_v2_chart/prod.yaml
autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 5
  targetCPUUtilizationPercentage: 80
```

Then upgrade:

```bash
./baas_controller.sh api-v2 upgrade
```

### Set Up Monitoring (Optional)

Consider setting up:
- Prometheus for metrics
- Grafana for dashboards
- Alerting for critical issues

### Set Up Log Aggregation (Optional)

Consider setting up:
- Centralized logging (Loki, ELK stack)
- Log retention policies
- Log analysis tools

## Next Steps

- [Upgrades](/docs/self-hosting/upgrades) - Learn how to upgrade your deployment
- [Troubleshooting](/docs/self-hosting/troubleshooting) - Common issues and solutions


---

## Self Hosting Overview

Deploy Meeting BaaS v2 in your own infrastructure with full control

### Source: ./content/docs/self-hosting/index.mdx


Meeting BaaS v2 supports self-hosted deployments, allowing you to run the platform in your own infrastructure with configurable feature flags. This gives you full control over your deployment while maintaining compatibility with the SaaS version.

## What You Get

When you self-host Meeting BaaS v2, you receive:

- **Container Images**: Pre-built Docker images for:
  - API Server (used for both API server and background jobs)
  - Zoom Bots (`zoom-bots-v2`)
  - Meet/Teams Bots (`meet-teams-bots-v2`)
  - Video Device Plugin (custom Kubernetes device plugin for v4l2 devices)

- **Helm Charts**: Production-ready Kubernetes Helm charts via the `kubernetes-config` repository
- **Deployment Scripts**: CLI tools for easy deployment and upgrades
- **Documentation**: Complete setup and configuration guides

## Deployment Architecture

Meeting BaaS v2 is designed to run on Kubernetes with the following components:

- **API Server**: Handles API requests, authentication, and business logic
- **Background Jobs**: CronJobs for scheduled tasks (bot creation, data retention, calendar sync)
- **Bot Pods**: Scalable pods that join meetings and record them
- **Video Device Plugin**: DaemonSet that provisions virtual video devices (v4l2loopback) for bots

### Component Interaction

The following diagram illustrates how the API server, bot pods, and video device plugin interact:

<Mermaid
  chart="
graph TB
    subgraph K8s[Kubernetes Cluster]
        API[API Server]
        Jobs[Background Jobs]
        Bot[Bot Pods]
        VDP[Video Device Plugin]
    end
    
    SQS[SQS Queue]
    S3[S3 Storage]
    DB[(PostgreSQL)]
    Redis[(Redis)]
    Zoom[Zoom]
    Meet[Google Meet]
    Teams[Microsoft Teams]
    
    API -->|Creates jobs| SQS
    API -->|Reads/Writes| DB
    API -->|Locks| Redis
    
    Jobs -->|Scheduled jobs| SQS
    Jobs -->|Data retention| DB
    
    SQS -->|Consumes| Bot
    VDP -->|Provides devices| Bot
    
    Bot -->|Joins meetings| Zoom
    Bot -->|Joins meetings| Meet
    Bot -->|Joins meetings| Teams
    Bot -->|Uploads| S3
"
/>

**How It Works**:

1. **API Server** receives bot creation requests and enqueues jobs to **SQS**
2. **Background Jobs** (CronJobs) also create bot jobs in **SQS** for scheduled meetings
3. **Bot Pods** (auto-scaled via KEDA) consume jobs from **SQS**
4. **Video Device Plugin** (DaemonSet) runs on bot pool nodes and provisions virtual video devices (`/dev/video*`) that bot pods require
5. **Bot Pods** use the video devices to join meetings on **Zoom**, **Google Meet**, or **Microsoft Teams**
6. **Bot Pods** record meetings and upload recordings to **S3**
7. **API Server** manages all metadata in **PostgreSQL** and uses **Redis** for deduplication

## Feature Flags

The platform uses feature flags to enable/disable functionality, making it easy to deploy only what you need:

| Flag | Description |
|------|-------------|
| `SELF_HOSTED` | Enable self-hosted mode (simplified configuration) |
| `ENABLE_STRIPE` | Enable Stripe billing and token system |
| `ENABLE_SVIX` | Enable SVIX managed webhooks |
| `ENABLE_CALENDAR` | Enable Google/Microsoft calendar integration |
| `ENABLE_MULTI_TENANT` | Enable multi-tenant mode (teams, multiple users) |
| `ENABLE_DASHBOARD` | Enable frontend dashboard (BFF and internal routes) |
| `ENABLE_TRANSCRIPTION` | Enable Gladia transcription service |
| `ENABLE_EMAIL` | Enable Resend email notifications |

All flags default to `false`, making minimal deployments straightforward.

## Deployment Modes

### Single-Tenant (Simplest)

Perfect for organizations that need a single team with unlimited usage:

- Static API key authentication
- No billing system
- Direct webhook callbacks (no SVIX needed)
- Single team with enterprise-level limits

### Multi-Tenant

For organizations that need multiple teams and user management:

- Team-based access control
- User invitations and management
- Per-team rate limits and quotas
- Optional Stripe billing integration

### With Dashboard

Add the frontend dashboard for a complete UI experience:

- Web-based bot management
- User authentication via OAuth
- Team management interface
- Usage analytics and monitoring

## Quick Start

1. **Review Prerequisites**: Ensure you have the required infrastructure
2. **Set Up Repository**: Clone the Helm charts and create your environment overrides
3. **Configure**: Set up environment variables and feature flags
4. **Deploy**: Use the provided deployment scripts to install the platform
5. **Upgrade**: Use CI/CD-friendly upgrade process for updates

## Next Steps

- [Prerequisites](/docs/self-hosting/prerequisites) - Infrastructure and requirements
- [Infrastructure Setup](/docs/self-hosting/infrastructure-setup) - Set up your Kubernetes cluster and services
- [Repository Setup](/docs/self-hosting/repository-setup) - Configure the Helm charts repository
- [Configuration](/docs/self-hosting/configuration) - Configure feature flags and environment variables
- [Deployment](/docs/self-hosting/deployment) - Deploy the platform
- [Upgrades](/docs/self-hosting/upgrades) - Keep your deployment up to date
- [Troubleshooting](/docs/self-hosting/troubleshooting) - Common issues and solutions


---

## Infrastructure Setup

Set up your Kubernetes cluster, databases, and services

### Source: ./content/docs/self-hosting/infrastructure-setup.mdx


This guide walks you through setting up the infrastructure required for Meeting BaaS v2.

## Architecture Overview

The following diagram illustrates the complete infrastructure architecture for Meeting BaaS v2:

<Mermaid chart={`flowchart TD
    Users[Users / API Clients]
    Users -->|HTTPS| Domain[api.yourcompany.com]
    Domain -->|DNS| LB[Load Balancer]
    LB --> NGINX[NGINX Ingress Controller]
    CertManager[cert-manager] -.->|SSL Certs| NGINX

    subgraph APIPool[" API Server Pool "]
        API[API Server Pods x2-3]
    end
    NGINX --> API

    subgraph DataStores[" Data Layer "]
        DB[(PostgreSQL)]
        Redis[(Redis)]
    end
    API <--> DB
    API <--> Redis

    subgraph Queues[" Message Queues "]
        SQSZoom[Zoom Queue]
        SQSMeet[Meet/Teams Queue]
    end
    API --> SQSZoom
    API --> SQSMeet

    subgraph BotPool[" Bot Node Pool "]
        subgraph ZoomBots[" Zoom Bots "]
            ZBot[Zoom Bot Pods]
        end
        subgraph MeetBots[" Meet/Teams Bots "]
            MTBot[Meet/Teams Bot Pods]
        end
        VideoPlugin[Video Device Plugin]
    end
    SQSZoom --> ZBot
    SQSMeet --> MTBot
    VideoPlugin -.-> ZBot
    VideoPlugin -.-> MTBot

    MeetingPlatforms[Meeting Platforms]
    ZBot <--> MeetingPlatforms
    MTBot <--> MeetingPlatforms

    subgraph Storage[" Object Storage "]
        S3[S3 Buckets]
    end
    ZBot --> S3
    MTBot --> S3
    ZBot <--> DB
    MTBot <--> DB

    subgraph CronJobs[" Background Jobs "]
        Cron[CronJobs]
    end
    Cron <--> DB
    Cron --> SQSZoom
    Cron --> SQSMeet

    subgraph Optional[" Optional Services "]
        Gladia[Gladia]
        Stripe[Stripe]
    end
    ZBot -.-> Gladia
    MTBot -.-> Gladia
    API -.-> Stripe

    classDef entry fill:#00dbc6,stroke:#0ea5a0,stroke-width:2px,color:#0f172a
    classDef api fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#1e40af
    classDef bot fill:#ede9fe,stroke:#8b5cf6,stroke-width:2px,color:#5b21b6
    classDef storage fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#92400e
    classDef queue fill:#fce7f3,stroke:#ec4899,stroke-width:2px,color:#9d174d
    classDef optional fill:#f1f5f9,stroke:#64748b,stroke-width:2px,color:#334155
    classDef external fill:#e0e7ff,stroke:#6366f1,stroke-width:2px,color:#3730a3

    class Users,Domain,LB,NGINX,CertManager entry
    class API,Cron api
    class ZBot,MTBot,VideoPlugin bot
    class DB,Redis,S3 storage
    class SQSZoom,SQSMeet queue
    class Gladia,Stripe optional
    class MeetingPlatforms external
`} />

### Key Architecture Components

**Kubernetes Cluster**:
- **API Server Pool**: Runs API servers and background CronJobs with horizontal pod autoscaling
- **Bot Node Pool**: Runs bot pods that join and record meetings, scales from 0 to 10+ nodes based on demand
- **Ingress Layer**: NGINX Ingress Controller with cert-manager for automatic SSL/TLS certificate management

**External Services**:
- **PostgreSQL**: Central database for all persistent data (users, teams, bots, configurations)
- **Redis**: Session storage, distributed locks, and caching
- **S3 Object Storage**: Stores recordings, logs, audio chunks, and assets
- **SQS Message Queues**: Decouples API from bot execution, enables elastic auto-scaling

**Data Flow**:
1. Users make API requests via HTTPS to your domain
2. DNS routes to the Kubernetes LoadBalancer
3. NGINX Ingress terminates SSL and routes to API server pods
4. API servers process requests, store data in PostgreSQL/Redis
5. When a bot is needed, API servers send jobs to SQS queues
6. KEDA monitors SQS queue depth and scales bot pods automatically
7. Bot pods receive jobs, join meetings, record, and upload to S3
8. Background CronJobs handle scheduled tasks, calendar sync, and data retention

## Kubernetes Cluster Setup

### 1. Create Node Pools

Create two node pools in your Kubernetes cluster:

#### API Server Pool

```bash
# Example for Scaleway Kapsule
scw k8s pool create \
  cluster-id=YOUR_CLUSTER_ID \
  name=api-server-pool \
  node-type=GP1-XS \
  size=2 \
  autoscaling=true \
  min-size=1 \
  max-size=3
```

**Configuration**:
- **Node Type**: General purpose (2-4 CPU, 4-8GB RAM)
- **Initial Size**: 1-2 nodes
- **Auto-scaling**: Enabled (min: 1, max: 3-5)
- **Labels**: `k8s.scaleway.com/pool-name=api-server-pool`

#### Bot Pool

```bash
scw k8s pool create \
  cluster-id=YOUR_CLUSTER_ID \
  name=bots-pool \
  node-type=DEV1-L \
  size=0 \
  autoscaling=true \
  min-size=0 \
  max-size=10
```

**Configuration**:
- **Node Type**: High-performance (16+ CPU, 32GB+ RAM)
- **Initial Size**: 0 nodes (scales based on demand)
- **Auto-scaling**: Enabled (min: 0, max: 10+)
- **Labels**: `k8s.scaleway.com/pool-name=bots-pool`

### 2. Install Ingress Controller

Install NGINX Ingress Controller:

```bash
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --create-namespace \
  --set controller.service.type=LoadBalancer
```

### 3. Install cert-manager (for SSL/TLS)

```bash
helm repo add jetstack https://charts.jetstack.io
helm repo update

helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set installCRDs=true
```

### 4. Create Namespace

```bash
kubectl create namespace services
```

## Database Setup

### PostgreSQL

Create a PostgreSQL database (version 14+):

**Required Databases**:
- Main database for Meeting BaaS v2

**Connection String Format**:
```
postgres://username:password@host:port/database_name
```

**Recommended Settings**:
- **Encoding**: UTF8
- **Timezone**: UTC
- **Max Connections**: 100+ (adjust based on your API server replica count)
- **Backup**: Enable automated backups

**Security**:
- Use strong passwords
- Restrict network access to Kubernetes cluster IPs only
- Enable SSL/TLS connections

## Redis Setup

Create a Redis instance:

**Configuration**:
- **Version**: 6.0+ or 7.0+
- **Memory**: 1GB+ (adjust based on usage)
- **Persistence**: Optional (AOF recommended for production)
- **TLS**: Enable if available

**Connection String Format**:
```
redis://username:password@host:port
# Or with TLS
rediss://username:password@host:port
```

**Security**:
- Use strong passwords
- Restrict network access to Kubernetes cluster IPs only
- Enable TLS if supported

## Object Storage (S3) Setup

### Create Buckets

Create the following buckets in your S3-compatible storage:

```bash
# Artifacts bucket (recordings)
aws s3 mb s3://your-company-meeting-baas-artifacts

# Logs bucket
aws s3 mb s3://your-company-meeting-baas-logs

# Audio chunks bucket (if transcription enabled)
aws s3 mb s3://your-company-meeting-baas-audio-chunks

# Logo bucket (optional)
aws s3 mb s3://your-company-meeting-baas-logo

# Support bucket (optional)
aws s3 mb s3://your-company-meeting-baas-support
```

### Configure CORS

Set up CORS for the artifacts bucket to allow direct downloads:

```json
[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "HEAD"],
    "AllowedOrigins": ["*"],
    "ExposeHeaders": ["Content-Length", "Content-Type"],
    "MaxAgeSeconds": 3600
  }
]
```

### Create Access Keys

Create IAM user or access keys with permissions:
- `s3:GetObject` - Read artifacts
- `s3:PutObject` - Upload recordings
- `s3:DeleteObject` - Delete old data
- `s3:ListBucket` - List objects

## Message Queue (SQS) Setup

### Create Queues

Create two SQS queues:

```bash
# Zoom bots queue
aws sqs create-queue \
  --queue-name meeting-baas-zoom \
  --attributes VisibilityTimeout=3600

# Meet/Teams bots queue
aws sqs create-queue \
  --queue-name meeting-baas-meet-teams \
  --attributes VisibilityTimeout=3600
```

**Queue Configuration**:
- **Visibility Timeout**: 3600 seconds (1 hour) - matches bot recording duration
- **Message Retention**: 14 days (default)
- **Dead Letter Queue**: Recommended for failed messages

### Create Access Keys

Create IAM user or access keys with permissions:
- `sqs:SendMessage` - Send bot jobs
- `sqs:ReceiveMessage` - Receive bot jobs
- `sqs:DeleteMessage` - Delete processed jobs
- `sqs:GetQueueAttributes` - Check queue depth

## Optional: EFS/NFS Server Setup

### Option 1: AWS EFS

```bash
# Create EFS file system
aws efs create-file-system \
  --creation-token meeting-baas-efs \
  --performance-mode generalPurpose \
  --throughput-mode provisioned \
  --provisioned-throughput-in-mibps 100

# Create mount targets in each availability zone
aws efs create-mount-target \
  --file-system-id fs-xxxxx \
  --subnet-id subnet-xxxxx \
  --security-groups sg-xxxxx
```

### Option 2: NFS Server on Elastic Metal/VM

Set up an NFS server:

```bash
# Install NFS server
sudo apt-get install nfs-kernel-server

# Create shared directory
sudo mkdir -p /shared
sudo chown nobody:nogroup /shared
sudo chmod 777 /shared

# Configure exports
echo "/shared *(rw,sync,no_subtree_check)" | sudo tee -a /etc/exports
sudo exportfs -a
```

**NFS Configuration**:
- **Version**: NFSv4 recommended
- **Options**: `vers=4,soft,timeo=30,retrans=3`
- **Security**: Restrict access to Kubernetes cluster IPs

## DNS Configuration

### Create DNS Records

Point your domain to the Kubernetes ingress:

```bash
# Get ingress IP
kubectl get svc -n ingress-nginx ingress-nginx-controller

# Create A record
# api.yourcompany.com -> <INGRESS_IP>
```

### Verify DNS

```bash
dig api.yourcompany.com
# Should return your ingress IP
```

## Video Device Plugin Requirements

The video device plugin requires:

- **Kernel modules**: `v4l2loopback` (loaded automatically by the plugin)
- **Privileged access**: Plugin runs as DaemonSet with privileged security context
- **Node labels**: Bot pool nodes must be labeled correctly

The plugin automatically:
- Loads `v4l2loopback` kernel module
- Creates virtual video devices
- Exposes them as Kubernetes resources (`meeting-baas.io/video-devices`)

## Security Considerations

### Network Security

- **Database**: Restrict access to Kubernetes cluster IPs only
- **Redis**: Restrict access to Kubernetes cluster IPs only
- **S3**: Use IAM policies to restrict bucket access
- **SQS**: Use IAM policies to restrict queue access

### Secrets Management

- Store sensitive credentials in Kubernetes Secrets (not in code)
- Use external secret management (AWS Secrets Manager, HashiCorp Vault) if available
- Rotate credentials regularly

### Pod Security

- Use Pod Security Standards (Restricted where possible)
- Video device plugin requires privileged access (unavoidable for v4l2)
- Bot pods require elevated capabilities for video device access

## Resource Planning

### API Server Pool

**Per Node**:
- CPU: 2-4 cores
- Memory: 4-8GB
- Storage: 20GB (for container images)

**Total** (3 nodes):
- CPU: 6-12 cores
- Memory: 12-24GB

### Bot Pool

**Per Node**:
- CPU: 16+ cores
- Memory: 32GB+
- Storage: 100GB+ (for container images and temporary files)

**Scaling**:
- Starts at 0 nodes
- Scales up based on SQS queue depth
- Each bot pod uses 1.5-4 CPU cores and 3-8GB RAM

## Verification Checklist

Before proceeding to repository setup, verify:

- [ ] Kubernetes cluster is accessible via `kubectl`
- [ ] Two node pools created (API server and bots)
- [ ] Ingress controller installed and has external IP
- [ ] cert-manager installed
- [ ] PostgreSQL database created and accessible
- [ ] Redis instance created and accessible
- [ ] S3 buckets created with proper permissions
- [ ] SQS queues created
- [ ] DNS records pointing to ingress IP
- [ ] EFS/NFS server set up (optional)
- [ ] All credentials documented securely

## Next Steps

- [Repository Setup](/docs/self-hosting/repository-setup) - Clone Helm charts and create environment overrides
- [Configuration](/docs/self-hosting/configuration) - Configure feature flags and environment variables


---

## Prerequisites

Infrastructure and requirements for self-hosting Meeting BaaS v2

### Source: ./content/docs/self-hosting/prerequisites.mdx


Before deploying Meeting BaaS v2, ensure you have the following infrastructure and tools in place.

## Infrastructure Requirements

### Kubernetes Cluster

You need a Kubernetes cluster (1.24+) with at least **2 node pools**:

#### API Server Pool
- **Purpose**: Runs API server pods and background job CronJobs
- **Recommended**: 1-3 nodes
- **Node Type**: General purpose (2-4 CPU, 4-8GB RAM per node)
- **Scaling**: Can use Horizontal Pod Autoscaler (HPA) for auto-scaling

#### Bot Pool
- **Purpose**: Runs bot pods that join and record meetings
- **Recommended**: 0-10+ nodes (scales based on demand)
- **Node Type**: High-performance (16+ CPU, 32GB+ RAM per node)
- **Scaling**: Uses KEDA for auto-scaling based on SQS queue depth
- **Special Requirements**: Requires video device plugin for v4l2 devices

### Database

**PostgreSQL 14+** database for storing:
- User accounts and authentication
- Team and organization data
- Bot configurations and metadata
- API keys and permissions
- Calendar connections (if enabled)
- Webhook configurations (if SVIX enabled)

**Recommended**: Managed PostgreSQL service (AWS RDS, Scaleway RDB, etc.)

### Object Storage

**S3-compatible object storage** for:
- Recorded video/audio files
- Bot logs and artifacts
- Team logos
- Support attachments

**Required Buckets**:
- `artifacts` - Recorded videos and audio
- `logs` - Bot execution logs
- `audio-chunks` - Temporary audio chunks (if transcription enabled)
- `logo` - Team logos (optional)
- `support` - Support ticket attachments (optional)

**Recommended**: AWS S3, Scaleway Object Storage, or any S3-compatible service

### Message Queue

**SQS-compatible message queue** for bot orchestration:
- Bot creation requests
- Job scheduling
- Event processing

**Required Queues**:
- `zoom` - Zoom bot jobs
- `meet-teams` - Google Meet and Microsoft Teams bot jobs

**Recommended**: AWS SQS, Scaleway Message Queue (MNQ), or any SQS-compatible service

### Redis

**Redis instance** for:
- Deduplication locks
- Session storage (if dashboard enabled)
- Caching

**Recommended**: Managed Redis service (AWS ElastiCache, Scaleway Redis Cloud, etc.)

### Optional: EFS/NFS Server

**Network File System** for bot data persistence:
- Acts as redundancy layer if S3 upload fails
- Temporary storage for recordings before upload
- Debug artifact collection

**Note**: This is optional but recommended for production deployments.

## Container Registry

You'll need access to:
- **Meeting BaaS Container Registry**: To pull images (we provide access)
- **Your Own Container Registry**: To push images if you want to mirror them

Images provided:
- `api-server-v2` - API server and jobs
- `zoom-bots-v2` - Zoom meeting bots
- `meet-teams-bots-v2` - Google Meet and Microsoft Teams bots
- `video-device-plugin` - Kubernetes device plugin

## Domain and DNS

- **Domain name** for your API endpoint (e.g., `api.yourcompany.com`)
- **DNS access** to create A/CNAME records pointing to your Kubernetes ingress
- **SSL/TLS certificates** (handled automatically via cert-manager and Let's Encrypt)

## Tools Required

### kubectl

Kubernetes command-line tool:

```bash
# macOS
brew install kubectl

# Linux
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
```

### Helm

Package manager for Kubernetes:

```bash
# macOS
brew install helm

# Linux
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
```

### Docker (Optional)

Only needed if you want to pull and push images to your own registry:

```bash
# macOS
brew install docker

# Linux
# Follow Docker installation guide for your distribution
```

### Git

For cloning the Helm charts repository:

```bash
# macOS
brew install git

# Linux
sudo apt-get install git  # Ubuntu/Debian
sudo dnf install git      # Fedora/RHEL
```

## Access Requirements

### Kubernetes Access

- **kubeconfig file** for your cluster
- **Cluster admin permissions** (or sufficient permissions to create namespaces, deployments, services, etc.)
- **Ingress controller** installed (NGINX recommended)

### Container Registry Access

- **Read access** to Meeting BaaS container registry (provided by us)
- **Write access** to your own container registry (if mirroring images)

### Service Credentials

You'll need credentials for:
- Database connection string
- Redis connection string
- S3 access keys and bucket names
- SQS access keys and queue URLs
- OAuth client IDs/secrets (if dashboard enabled)
- API keys for optional services (Stripe, SVIX, Gladia, Resend)

## Feature Flag Considerations

Before deployment, decide which features you need:

### Minimal Deployment (Single-Tenant)

- `SELF_HOSTED=true`
- `ENABLE_MULTI_TENANT=false`
- All other flags `false`

**Requires**: Database, Redis, S3, SQS, Kubernetes cluster

### With Dashboard

- `SELF_HOSTED=true`
- `ENABLE_DASHBOARD=true`
- `ENABLE_MULTI_TENANT=false` (or `true` for multi-tenant)

**Requires**: Everything above + OAuth credentials (Google/GitHub)

### With Calendar Integration

- `ENABLE_CALENDAR=true`

**Requires**: Google/Microsoft OAuth credentials, calendar encryption key

### With Transcription

- `ENABLE_TRANSCRIPTION=true`

**Requires**: Gladia API key, audio chunks S3 bucket

### With Email Notifications

- `ENABLE_EMAIL=true`

**Requires**: Resend API key

### With Managed Webhooks (SVIX)

- `ENABLE_SVIX=true`

**Requires**: SVIX instance URL and JWT secret

### With Billing (Stripe)

- `ENABLE_STRIPE=true`

**Requires**: Stripe account, webhook secret, product/price IDs

## Network Requirements

### Ingress

- **Port 443** (HTTPS) exposed to the internet
- **Port 80** (HTTP) for Let's Encrypt ACME challenges
- **Ingress controller** (NGINX recommended)

### Internal Communication

- **Pod-to-pod communication** within the cluster
- **Service discovery** via Kubernetes DNS
- **SQS endpoint** accessible from pods

### External Services

- **Database** accessible from Kubernetes pods
- **Redis** accessible from Kubernetes pods
- **S3 endpoint** accessible from Kubernetes pods
- **SQS endpoint** accessible from Kubernetes pods

## Resource Estimates

### API Server Pool

- **CPU**: 0.5-1.5 cores per pod (2-5 pods recommended)
- **Memory**: 1-5GB per pod
- **Storage**: Minimal (no persistent volumes needed)

### Bot Pool

- **CPU**: 1.5-4 cores per bot pod
- **Memory**: 3-8GB per bot pod
- **Storage**: Optional EFS/NFS mount for temporary files
- **Video Devices**: 1 device per bot pod (managed by video device plugin)

### Background Jobs

- **CPU**: 0.1-0.5 cores per job
- **Memory**: 200MB-1GB per job
- **Frequency**: Varies by job (see [Background Jobs](/docs/self-hosting/configuration#background-jobs))

## Next Steps

Once you have all prerequisites in place:

1. [Set up your infrastructure](/docs/self-hosting/infrastructure-setup) - Configure Kubernetes, databases, and services
2. [Set up the repository](/docs/self-hosting/repository-setup) - Clone Helm charts and create environment overrides
3. [Configure the platform](/docs/self-hosting/configuration) - Set feature flags and environment variables


---

## Repository Setup

Set up the Helm charts repository and environment overrides

### Source: ./content/docs/self-hosting/repository-setup.mdx


This guide explains how to set up the Helm charts repository and create your environment-specific configuration files.

## Overview

Meeting BaaS provides Helm charts via a separate repository (`kubernetes-config`). You'll create your own repository that includes these charts as a git submodule and add your environment-specific overrides.

## Repository Structure

Create the following structure:

```text
your-company-meeting-baas-config/
├── helm-charts/                 # Git submodule (kubernetes-config repo)
│   ├── api_server_v2_chart/
│   ├── job_v2_chart/
│   ├── zoom_bots_v2_chart/
│   ├── meet_teams_bots_v2_chart/
│   ├── video_device_plugin_chart/
│   └── baas_controller.sh
├── environment-overrides/       # Your environment-specific configs
│   ├── api_server_v2_chart/
│   │   └── prod.yaml
│   ├── job_v2_chart/
│   │   └── prod.yaml
│   ├── zoom_bots_v2_chart/
│   │   └── prod.yaml
│   ├── meet_teams_bots_v2_chart/
│   │   └── prod.yaml
│   └── video_device_plugin_chart/
│       └── prod.yaml
├── k8s-resources/              # Kubernetes resources (certificates, etc.)
│   └── prod-certs/
│       ├── cluster-issuer.yaml
│       └── cluster-certificate.yaml
└── kubeconfig.yaml             # Your Kubernetes config (gitignored)
```

## Step 1: Create Your Repository

```bash
# Create new directory
mkdir your-company-meeting-baas-config
cd your-company-meeting-baas-config

# Initialize git repository
git init
```

## Step 2: Add Helm Charts as Submodule

Add the Meeting BaaS kubernetes-config repository as a git submodule:

```bash
# Add submodule
git submodule add https://github.com/Meeting-BaaS/kubernetes-config.git helm-charts

# Initialize and update submodule
git submodule update --init --recursive
```

**Note**: You'll need access to the `kubernetes-config` repository. Contact your Meeting BaaS representative for access.

## Step 3: Create Environment Overrides Directory

```bash
# Create directory structure
mkdir -p environment-overrides/{api_server_v2_chart,job_v2_chart,zoom_bots_v2_chart,meet_teams_bots_v2_chart,video_device_plugin_chart}

# Create k8s-resources directory
mkdir -p k8s-resources/prod-certs
```

## Step 4: Create .gitignore

Create a `.gitignore` file to exclude sensitive files:

```text
# Kubernetes config (contains credentials)
kubeconfig.yaml
kubeconfig-*.yaml

# Environment overrides (may contain secrets)
# Uncomment if you want to keep them private:
# environment-overrides/**/*.yaml

# Git submodule tracking
.git/modules/

# IDE files
.vscode/
.idea/
*.swp
*.swo
*~

# OS files
.DS_Store
Thumbs.db
```

## Step 5: Create Environment Override Files

You'll receive template `prod.yaml` files from Meeting BaaS. Create these files in your `environment-overrides` directory:

### api_server_v2_chart/prod.yaml

```yaml
image:
  repository: YOUR_REGISTRY/api-server-v2  # Update to your registry
  pullPolicy: Always

# Feature flags - customize based on your needs
featureFlags:
  selfHosted: true
  enableStripe: false
  enableSvix: false
  enableCalendar: false
  enableMultitenant: false
  enableDashboard: false
  enableTranscription: false
  enableEmail: false

# Self-hosted configuration
selfHosted:
  staticApiKey: "your-secret-api-key"  # Generate a secure key
  staticTeamId: "your-team-id"         # Choose a team identifier

# Node selector - update with your pool name
nodeSelector:
  k8s.scaleway.com/pool-name: YOUR_API_POOL_NAME

# Secrets - fill in your values
secret:
  database_url: "postgres://..."
  redis_url: "redis://..."
  # ... other secrets

# ConfigMap - update with your values
configmap:
  api_server_baseurl: "https://api.yourcompany.com"
  frontend_baseurl: "https://dashboard.yourcompany.com"
  # ... other config values
```

### job_v2_chart/prod.yaml

```yaml
image:
  repository: YOUR_REGISTRY/api-server-v2  # Same as API server
  pullPolicy: Always

# Feature flags - should match api_server_v2_chart
featureFlags:
  enableCalendar: false
  enableMultitenant: false
  enableTranscription: false
  enableEmail: false

# Node selector - update with your pool name
nodeSelector:
  k8s.scaleway.com/pool-name: YOUR_API_POOL_NAME

# Secrets - fill in your values
secret:
  database_url: "postgres://..."
  # ... other secrets
```

### zoom_bots_v2_chart/prod.yaml

```yaml
image:
  repository: YOUR_REGISTRY/zoom-bots-v2  # Update to your registry
  pullPolicy: Always

# Node selector - update with your bots pool name
nodeSelector:
  k8s.scaleway.com/pool-name: YOUR_BOTS_POOL_NAME

# Secrets - fill in your values
secrets:
  sqs_queue_url: "https://sqs..."
  # ... other secrets
```

### meet_teams_bots_v2_chart/prod.yaml

```yaml
image:
  repository: YOUR_REGISTRY/meet-teams-bots-v2  # Update to your registry
  pullPolicy: Always

# Node selector - update with your bots pool name
nodeSelector:
  k8s.scaleway.com/pool-name: YOUR_BOTS_POOL_NAME

# Secrets - fill in your values
secrets:
  sqs_queue_url: "https://sqs..."
  # ... other secrets
```

### video_device_plugin_chart/prod.yaml

```yaml
image:
  repository: YOUR_REGISTRY/video-device-plugin  # Update to your registry
  pullPolicy: IfNotPresent
  tag: "1.0.0"

# Node selector - update with your bots pool name
nodeSelector:
  k8s.scaleway.com/pool-name: YOUR_BOTS_POOL_NAME
```

## Step 6: Update baas_controller.sh

The `baas_controller.sh` script needs to know your Kubernetes context. Update it:

```bash
# Find your kubectl context name
kubectl config get-contexts

# Edit baas_controller.sh
# Replace 'admin@meeting-baas-prod-k8' with your context name
# Or update the context switching logic to match your setup
```

**Note**: The script automatically detects if you're running from the root directory or `helm-charts` directory.

## Step 7: Configure Kubernetes Resources

### Cluster Issuer (cert-manager)

Create `k8s-resources/prod-certs/cluster-issuer.yaml`:

```yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-cluster-issuer
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: your-email@yourcompany.com  # Update with your email
    privateKeySecretRef:
      name: letsencrypt-cluster-issuer-key
    solvers:
      - http01:
          ingress:
            class: nginx
```

### Certificate

Create `k8s-resources/prod-certs/cluster-certificate.yaml`:

```yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: api-meeting-baas-tls
  namespace: services
spec:
  secretName: api-meeting-baas-tls
  issuerRef:
    name: letsencrypt-cluster-issuer
    kind: ClusterIssuer
  dnsNames:
    - api.yourcompany.com  # Update with your domain
```

## Step 8: Add kubeconfig

Download your Kubernetes config file:

```bash
# For Scaleway Kapsule
scw k8s kubeconfig get YOUR_CLUSTER_ID > kubeconfig.yaml

# Or download from your cloud provider's console
```

**Important**: Add `kubeconfig.yaml` to `.gitignore` (already done above).

## Step 9: Verify Setup

```bash
# Verify kubectl can connect
kubectl --kubeconfig=kubeconfig.yaml get nodes

# Verify Helm charts are accessible
ls helm-charts/api_server_v2_chart/

# Verify environment overrides exist
ls environment-overrides/api_server_v2_chart/prod.yaml
```

## Step 10: Commit to Git (Optional)

If you want to version control your configuration:

```bash
# Add files (excluding secrets)
git add helm-charts/
git add environment-overrides/  # Only if not gitignored
git add k8s-resources/
git add .gitignore
git add .gitmodules

# Commit
git commit -m "Initial Meeting BaaS v2 configuration"

# Push to your repository
git remote add origin YOUR_REPO_URL
git push -u origin main
```

**Security Note**: Consider keeping `environment-overrides` in a private repository or using a secrets management tool.

## Updating Helm Charts

When Meeting BaaS updates the Helm charts:

```bash
# Update submodule to latest
cd helm-charts
git pull origin main
cd ..

# Review changes
git diff helm-charts

# Commit the update
git add helm-charts
git commit -m "Update Helm charts to latest version"
```

## Customizing Charts

If you need to customize the Helm charts:

1. **Fork the repository**: Create your own fork of `kubernetes-config`
2. **Use your fork**: Update the submodule to point to your fork
3. **Make changes**: Modify charts as needed
4. **Keep in sync**: Periodically merge updates from the upstream repository

**Note**: Customizing charts makes upgrades more complex. Prefer using environment overrides when possible.

## Next Steps

- [Configuration](/docs/self-hosting/configuration) - Configure feature flags and environment variables
- [Deployment](/docs/self-hosting/deployment) - Deploy the platform to your cluster


---

## Troubleshooting

Common issues and solutions for self-hosted deployments

### Source: ./content/docs/self-hosting/troubleshooting.mdx


This guide covers common issues you might encounter when self-hosting Meeting BaaS v2 and how to resolve them.

## Checking Deployment Status

### Verify All Services

```bash
# Check all deployments
kubectl get deployments -n services

# Check all pods
kubectl get pods -n services

# Check CronJobs
kubectl get cronjobs -n services

# Check ScaledJobs (KEDA)
kubectl get scaledjobs -n services

# Check DaemonSets
kubectl get daemonsets -n services
```

### Check Service Logs

```bash
# API server logs
kubectl logs -n services -l app.kubernetes.io/instance=api-server-v2 --tail=100

# Job logs (latest execution)
kubectl logs -n services -l cron=scheduled-bot-job --tail=100

# Bot pod logs (if any running)
kubectl logs -n services -l app.kubernetes.io/instance=zoom-bots-v2 --tail=100
```

### Check Feature Flags

```bash
# Public endpoint (no auth)
curl https://api.yourcompany.com/status/features

# Should return:
# {
#   "selfHosted": true,
#   "features": {
#     "stripe": false,
#     "svix": false,
#     "calendar": false,
#     "multitenant": false,
#     "dashboard": false,
#     "transcription": false,
#     "email": false
#   }
# }
```

## Common Issues

### API Server Not Starting

#### Symptoms

- Pods stuck in `CrashLoopBackOff` or `Pending` state
- Health endpoint not responding

#### Diagnosis

```bash
# Check pod status
kubectl describe pod -n services -l app.kubernetes.io/instance=api-server-v2

# Check logs
kubectl logs -n services -l app.kubernetes.io/instance=api-server-v2 --tail=100

# Check events
kubectl get events -n services --sort-by='.lastTimestamp'
```

#### Common Causes and Solutions

**1. Missing or Invalid Secrets**

```bash
# Check if secrets exist
kubectl get secrets -n services api-server-v2

# Verify secret keys
kubectl get secret -n services api-server-v2 -o jsonpath='{.data}' | jq 'keys'

# Common missing secrets:
# - DATABASE_URL
# - REDIS_URL
# - STATIC_API_KEY (if single-tenant)
```

**Solution**: Ensure all required secrets are set in `environment-overrides/api_server_v2_chart/prod.yaml`

**2. Database Connection Issues**

```bash
# Test database connection from pod
kubectl run -it --rm --restart=Never db-test \
  --image=postgres:14 \
  --env="PGPASSWORD=password" \
  -- psql -h YOUR_DB_HOST -U username -d database_name
```

**Solution**: 
- Verify `DATABASE_URL` is correct
- Check database firewall rules allow Kubernetes cluster IPs
- Verify database credentials are correct

**3. Invalid Configuration Values**

```bash
# Check ConfigMap
kubectl get configmap -n services api-server-v2 -o yaml

# Look for invalid values like:
# - Empty required fields
# - Invalid URLs
# - Wrong data types
```

**Solution**: Review `environment-overrides/api_server_v2_chart/prod.yaml` configmap section

**4. Image Pull Errors**

```bash
# Check pod events
kubectl describe pod -n services -l app.kubernetes.io/instance=api-server-v2 | grep -A 5 Events

# Common errors:
# - ImagePullBackOff: Cannot pull image
# - ErrImagePull: Authentication failed
```

**Solution**:
- Verify image registry credentials in `imagePullSecrets`
- Check image tag exists: `docker pull YOUR_REGISTRY/api-server-v2:$IMAGE_TAG`
- Verify network connectivity to registry

### Migration Job Failed

#### Symptoms

- Upgrade stuck waiting for migration
- Migration job in `Failed` state

#### Diagnosis

```bash
# Check migration job
kubectl get jobs -n services | grep migration

# Check migration logs
kubectl logs -n services -l app.kubernetes.io/component=migration --tail=100

# Check job details
kubectl describe job -n services api-server-v2-migration
```

#### Common Causes and Solutions

**1. Database Connection Issues**

**Solution**: Same as API server database connection issues above

**2. Migration Conflicts**

**Solution**: 
- Check if manual schema changes conflict with migrations
- Review migration logs for specific errors
- Contact Meeting BaaS support if migrations are incompatible

**3. Insufficient Permissions**

**Solution**: Ensure database user has permissions to:
- Create tables
- Alter tables
- Create indexes
- Create sequences

**4. Migration Timeout**

**Solution**: Increase timeout in `api_server_v2_chart/values.yaml`:

```yaml
migration:
  activeDeadlineSeconds: 600  # Increase from 300 to 600 seconds
```

### Bots Not Starting

#### Symptoms

- No bot pods running
- SQS queue has messages but no bots processing them

#### Diagnosis

```bash
# Check ScaledJob status
kubectl get scaledjobs -n services

# Check KEDA operator
kubectl get pods -n keda-system

# Check SQS queue depth
# (Use AWS CLI or your SQS provider's tool)

# Check bot pod events
kubectl get events -n services --field-selector involvedObject.kind=Pod
```

#### Common Causes and Solutions

**1. KEDA Not Installed**

```bash
# Check if KEDA is installed
kubectl get pods -n keda-system

# Install KEDA if missing
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda --namespace keda-system --create-namespace
```

**2. SQS Credentials Invalid**

**Solution**: Verify SQS credentials in bot chart secrets:
- `AWS_ACCESS_KEY_ID_SQS`
- `AWS_SECRET_ACCESS_KEY_SQS`
- `SQS_QUEUE_URL_ZOOM` or `SQS_QUEUE_URL_MEET_TEAMS`

**3. Video Device Plugin Not Running**

```bash
# Check video device plugin
kubectl get daemonset -n services video-device-plugin

# Check if devices are available
kubectl get nodes -o jsonpath='{.items[*].status.allocatable.meeting-baas\.io/video-devices}'
```

**Solution**: Ensure video device plugin is deployed and running on bot pool nodes

**4. Insufficient Node Resources**

```bash
# Check node resources
kubectl describe nodes | grep -A 5 "Allocated resources"

# Check if nodes can schedule pods
kubectl get nodes
```

**Solution**: 
- Add more nodes to bot pool
- Increase node resources
- Check resource requests/limits in bot chart

### Health Checks Failing

#### Symptoms

- Pods restarting frequently
- `kubectl get pods` shows `CrashLoopBackOff`

#### Diagnosis

```bash
# Check liveness probe
kubectl describe pod -n services -l app.kubernetes.io/instance=api-server-v2 | grep -A 10 Liveness

# Test endpoints manually
kubectl port-forward -n services svc/api-server-v2 3001:3001
# In another terminal:
curl http://localhost:3001/health
curl http://localhost:3001/liveness
```

#### Common Causes and Solutions

**1. Application Not Ready**

**Solution**: Check application logs for startup errors

**2. Port Mismatch**

**Solution**: Verify `configmap.port` matches container port (default: 3001)

**3. Slow Startup**

**Solution**: Increase initial delay in deployment:

```yaml
# In api_server_v2_chart/values.yaml or deployment template
livenessProbe:
  initialDelaySeconds: 30  # Increase from 20
readinessProbe:
  initialDelaySeconds: 15   # Increase from 10
```

### Feature Flags Not Working

#### Symptoms

- Features enabled but not available
- Routes returning 404 or 501

#### Diagnosis

```bash
# Check feature flags
curl https://api.yourcompany.com/status/features

# Check ConfigMap
kubectl get configmap -n services api-server-v2 -o yaml | grep ENABLE_

# Check if routes are registered
kubectl logs -n services -l app.kubernetes.io/instance=api-server-v2 | grep "Starting API server"
```

#### Common Causes and Solutions

**1. Feature Flags Not Set in ConfigMap**

**Solution**: Verify feature flags in `environment-overrides/api_server_v2_chart/prod.yaml`:

```yaml
featureFlags:
  enableCalendar: true  # Must be true (not "true" string)
```

**2. Job Chart Flags Don't Match**

**Solution**: Ensure `job_v2_chart/prod.yaml` feature flags match `api_server_v2_chart/prod.yaml`

**3. Routes Not Registered**

**Solution**: Check API server logs for route registration messages. Routes are conditionally registered based on feature flags.

### Database Connection Errors

#### Symptoms

- API server logs show database connection errors
- Migrations failing

#### Diagnosis

```bash
# Test database connection
kubectl run -it --rm --restart=Never db-test \
  --image=postgres:14 \
  --env="PGPASSWORD=password" \
  -- psql -h YOUR_DB_HOST -U username -d database_name -c "SELECT 1"
```

#### Common Causes and Solutions

**1. Firewall Rules**

**Solution**: Ensure database firewall allows connections from Kubernetes cluster IPs

**2. SSL/TLS Issues**

**Solution**: 
- Verify `DATABASE_URL` includes SSL parameters if required
- Check if `?sslmode=require` is needed
- Verify CA certificates if using custom certificates

**3. Connection Pool Exhausted**

**Solution**: 
- Increase database `max_connections`
- Reduce API server replica count
- Check for connection leaks in application

### S3 Upload Failures

#### Symptoms

- Recordings not appearing in S3
- Bot logs show upload errors

#### Diagnosis

```bash
# Check bot logs
kubectl logs -n services -l app.kubernetes.io/instance=zoom-bots-v2 --tail=100 | grep -i s3

# Test S3 access
kubectl run -it --rm --restart=Never s3-test \
  --image=amazon/aws-cli \
  --env="AWS_ACCESS_KEY_ID=..." \
  --env="AWS_SECRET_ACCESS_KEY=..." \
  --env="AWS_ENDPOINT_URL=..." \
  -- s3 ls s3://your-bucket-name
```

#### Common Causes and Solutions

**1. Invalid Credentials**

**Solution**: Verify S3 access keys and secret keys

**2. Bucket Not Found**

**Solution**: Verify bucket names match exactly (case-sensitive)

**3. Permissions Issues**

**Solution**: Ensure IAM user/keys have:
- `s3:PutObject` permission
- `s3:GetObject` permission (for retries)
- `s3:ListBucket` permission

**4. Endpoint URL Incorrect**

**Solution**: Verify `AWS_ENDPOINT_URL` matches your S3 provider:
- AWS: `https://s3.region.amazonaws.com`
- Scaleway: `https://s3.region.scw.cloud`
- MinIO: `http://minio-host:9000`

### Webhook Callbacks Not Working

#### Symptoms

- Bots complete but callbacks not received
- Callback logs show errors

#### Diagnosis

```bash
# Check API server logs for callback attempts
kubectl logs -n services -l app.kubernetes.io/instance=api-server-v2 | grep -i callback

# Test callback endpoint manually
curl -X POST https://your-callback-url.com/webhook \
  -H "Content-Type: application/json" \
  -d '{"test": "data"}'
```

#### Common Causes and Solutions

**1. Callback URL Not Accessible**

**Solution**: 
- Verify callback URL is publicly accessible
- Check firewall rules
- Verify SSL certificate is valid

**2. Callback Secret Mismatch**

**Solution**: Verify `callback_config.secret` matches what your server expects

**3. Network Timeout**

**Solution**: 
- Increase callback timeout in bot configuration
- Check network connectivity from Kubernetes cluster
- Verify DNS resolution for callback URL

### Background Jobs Not Running

#### Symptoms

- CronJobs exist but not executing
- No job pods created

#### Diagnosis

```bash
# Check CronJob status
kubectl get cronjobs -n services

# Check CronJob details
kubectl describe cronjob -n services scheduled-bot-job

# Check if jobs are being created
kubectl get jobs -n services

# Check job logs
kubectl logs -n services -l cron=scheduled-bot-job --tail=100
```

#### Common Causes and Solutions

**1. CronJob Not Created**

**Solution**: Check if feature flags allow the job to be created:
- Calendar jobs require `ENABLE_CALENDAR=true`
- Team deletion job requires `ENABLE_MULTI_TENANT=true`

**2. Job Failing Immediately**

**Solution**: Check job logs for errors (database connection, missing secrets, etc.)

**3. Cron Schedule Not Met**

**Solution**: 
- Verify CronJob schedule is correct
- Check Kubernetes cluster time is synchronized
- Wait for next scheduled time

## Getting Help

### Information to Provide

When seeking help, provide:

1. **Kubernetes Version**: `kubectl version`
2. **Helm Version**: `helm version`
3. **Feature Flags**: Output of `curl https://api.yourcompany.com/status/features`
4. **Pod Status**: `kubectl get pods -n services`
5. **Recent Logs**: `kubectl logs -n services -l app.kubernetes.io/instance=api-server-v2 --tail=100`
6. **Events**: `kubectl get events -n services --sort-by='.lastTimestamp'`
7. **Configuration**: Feature flags and relevant environment variables (redact secrets)

### Contact Support

- **Email**: support@meetingbaas.com
- **Documentation**: Check this troubleshooting guide first
- **Logs**: Always include relevant logs when reporting issues

## Prevention Tips

### Regular Monitoring

- Set up monitoring for pod health
- Monitor database connections
- Track SQS queue depth
- Monitor S3 upload success rates

### Regular Backups

- Backup database regularly
- Keep configuration files in version control
- Document any custom changes

### Stay Updated

- Keep Helm charts updated
- Apply security patches promptly
- Review release notes before upgrading

## Next Steps

- Review [Configuration Guide](/docs/self-hosting/configuration) for feature flag setup
- Check [Deployment Guide](/docs/self-hosting/deployment) for deployment steps
- Review [Upgrades Guide](/docs/self-hosting/upgrades) for upgrade procedures


---

## Upgrades

Upgrade your Meeting BaaS v2 deployment with CI/CD-friendly process

### Source: ./content/docs/self-hosting/upgrades.mdx


Meeting BaaS v2 deployments are designed to be CI/CD-friendly. This guide explains how to upgrade your deployment when new images are released.

## Upgrade Process Overview

The upgrade process is straightforward:

1. **Receive Image Tag**: Meeting BaaS provides new image tags periodically
2. **Set Environment Variables**: Export `ENVIRON` and `IMAGE_TAG`
3. **Run Upgrade Script**: Use `baas_controller.sh` to upgrade services
4. **Verify**: Check that services are running correctly

## Prerequisites

- Access to the latest image tag from Meeting BaaS
- `kubectl` configured and connected to your cluster
- `helm` installed
- Deployment script (`baas_controller.sh`) accessible

## Upgrade Steps

### Step 1: Set Environment Variables

```bash
# Set environment (prod or preprod)
export ENVIRON=prod

# Set the new image tag (provided by Meeting BaaS)
export IMAGE_TAG=2026-01-27abc123def456...
```

**Note**: The same `IMAGE_TAG` is used for all services (API server, jobs, and bots) since they're built from the same codebase commit.

### Step 2: Pull and Push Images (If Using Your Own Registry)

If you're mirroring images to your own registry:

```bash
# Login to Meeting BaaS registry
docker login rg.fr-par.scw.cloud

# Pull new images
docker pull rg.fr-par.scw.cloud/meeting-baas-prod-api-server/api-server-v2:$IMAGE_TAG
docker pull rg.fr-par.scw.cloud/meeting-baas-prod-bots/zoom-bots-v2:$IMAGE_TAG
docker pull rg.fr-par.scw.cloud/meeting-baas-prod-bots/meet-teams-bots-v2:$IMAGE_TAG

# Tag for your registry
docker tag rg.fr-par.scw.cloud/meeting-baas-prod-api-server/api-server-v2:$IMAGE_TAG \
  YOUR_REGISTRY/api-server-v2:$IMAGE_TAG
docker tag rg.fr-par.scw.cloud/meeting-baas-prod-bots/zoom-bots-v2:$IMAGE_TAG \
  YOUR_REGISTRY/zoom-bots-v2:$IMAGE_TAG
docker tag rg.fr-par.scw.cloud/meeting-baas-prod-bots/meet-teams-bots-v2:$IMAGE_TAG \
  YOUR_REGISTRY/meet-teams-bots-v2:$IMAGE_TAG

# Push to your registry
docker push YOUR_REGISTRY/api-server-v2:$IMAGE_TAG
docker push YOUR_REGISTRY/zoom-bots-v2:$IMAGE_TAG
docker push YOUR_REGISTRY/meet-teams-bots-v2:$IMAGE_TAG
```

### Step 3: Upgrade Services

Navigate to the helm-charts directory and upgrade each service:

```bash
cd helm-charts

export ENVIRON=prod
export IMAGE_TAG=2026-01-27abc123def456...

# Upgrade API server (includes migration job)
./baas_controller.sh api-v2 upgrade

# Upgrade background jobs
./baas_controller.sh job-v2 upgrade

# Upgrade bot services
./baas_controller.sh zoom-bots-v2 upgrade
./baas_controller.sh meet-teams-bots-v2 upgrade
```

**What Happens During Upgrade**:

1. **Migration Job**: Runs automatically as a pre-upgrade hook (if migrations exist)
2. **Rolling Update**: Pods are updated one at a time (zero-downtime)
3. **Health Checks**: New pods must pass health checks before old pods are terminated

### Step 4: Verify Upgrade

```bash
# Check API server pods
kubectl get pods -n services -l app.kubernetes.io/instance=api-server-v2

# Check pod images (should show new tag)
kubectl get pods -n services -l app.kubernetes.io/instance=api-server-v2 -o jsonpath='{.items[*].spec.containers[*].image}'

# Check API server logs for errors
kubectl logs -n services -l app.kubernetes.io/instance=api-server-v2 --tail=50

# Test health endpoint
curl https://api.yourcompany.com/health

# Test feature flags endpoint
curl https://api.yourcompany.com/status/features
```

## Database Migrations

Database migrations run automatically during API server upgrades:

### How It Works

1. Helm creates a migration Job before upgrading the deployment
2. The Job runs `node dist/cli/migrate.js` using the new image
3. The Job completes (or fails after retries) before pods roll out
4. Old Job is cleaned up automatically

### Migration Job Details

**Configuration** (in `api_server_v2_chart/values.yaml`):

```yaml
migration:
  enabled: true                    # Enable automatic migrations
  backoffLimit: 3                  # Retries on failure
  activeDeadlineSeconds: 300       # Timeout (5 minutes)
  ttlSecondsAfterFinished: 600     # Cleanup after (10 minutes)
```

### Checking Migration Status

```bash
# Check migration job
kubectl get jobs -n services | grep migration

# Check migration logs
kubectl logs -n services -l app.kubernetes.io/component=migration --tail=100

# Check migration job details
kubectl describe job -n services api-server-v2-migration
```

### Migration Failures

If migration fails:

1. **Check Logs**: Review migration job logs for errors
2. **Fix Issues**: Address database connectivity or permission issues
3. **Retry**: Delete the failed job and upgrade again:

```bash
# Delete failed migration job
kubectl delete job -n services api-server-v2-migration

# Retry upgrade
./baas_controller.sh api-v2 upgrade
```

## CI/CD Integration

### GitHub Actions Example

```yaml
name: Upgrade Meeting BaaS

on:
  workflow_dispatch:
    inputs:
      image_tag:
        description: 'Image tag to deploy'
        required: true

jobs:
  upgrade:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          submodules: recursive
      
      - name: Set up kubectl
        uses: azure/setup-kubectl@v3
      
      - name: Set up Helm
        uses: azure/setup-helm@v3
      
      - name: Configure kubectl
        run: |
          echo "${{ secrets.KUBECONFIG }}" > kubeconfig.yaml
          export KUBECONFIG=$PWD/kubeconfig.yaml
      
      - name: Upgrade services
        env:
          ENVIRON: prod
          IMAGE_TAG: ${{ github.event.inputs.image_tag }}
        run: |
          cd helm-charts
          chmod +x baas_controller.sh
          ./baas_controller.sh api-v2 upgrade
          ./baas_controller.sh job-v2 upgrade
          ./baas_controller.sh zoom-bots-v2 upgrade
          ./baas_controller.sh meet-teams-bots-v2 upgrade
      
      - name: Verify deployment
        run: |
          export KUBECONFIG=$PWD/kubeconfig.yaml
          kubectl get pods -n services
          curl -f https://api.yourcompany.com/health || exit 1
```

### GitLab CI Example

```yaml
upgrade:
  stage: deploy
  script:
    - export ENVIRON=prod
    - export IMAGE_TAG=$CI_COMMIT_TAG
    - cd helm-charts
    - chmod +x baas_controller.sh
    - ./baas_controller.sh api-v2 upgrade
    - ./baas_controller.sh job-v2 upgrade
    - ./baas_controller.sh zoom-bots-v2 upgrade
    - ./baas_controller.sh meet-teams-bots-v2 upgrade
  only:
    - tags
```

## Rollback Procedure

If an upgrade causes issues, you can rollback:

### Rollback API Server

```bash
# List revisions
helm history api-server-v2 -n services

# Rollback to previous revision
helm rollback api-server-v2 -n services

# Or rollback to specific revision
helm rollback api-server-v2 3 -n services
```

### Rollback Other Services

```bash
# Rollback jobs
helm rollback job-v2 -n services

# Rollback bots
helm rollback zoom-bots-v2 -n services
helm rollback meet-teams-bots-v2 -n services
```

**Note**: Rolling back may require database migrations to be compatible. Check migration compatibility before rolling back.

## Upgrade Best Practices

### Pre-Upgrade Checklist

- [ ] Review release notes for breaking changes
- [ ] Backup database (if possible)
- [ ] Test upgrade in staging environment first
- [ ] Verify image tag is correct
- [ ] Check that all required secrets are still valid
- [ ] Ensure sufficient cluster resources

### During Upgrade

- Monitor pod status: `kubectl get pods -n services -w`
- Watch API server logs: `kubectl logs -n services -l app.kubernetes.io/instance=api-server-v2 -f`
- Check migration job: `kubectl get jobs -n services | grep migration`
- Test health endpoint: `curl https://api.yourcompany.com/health`

### Post-Upgrade Verification

- [ ] All pods are running: `kubectl get pods -n services`
- [ ] Health endpoint responds: `curl https://api.yourcompany.com/health`
- [ ] Feature flags are correct: `curl https://api.yourcompany.com/status/features`
- [ ] Test bot creation: Create a test bot via API
- [ ] Check logs for errors: Review recent logs for any issues
- [ ] Verify background jobs: Check that CronJobs are running

## Updating Helm Charts

When Helm charts are updated:

```bash
# Update submodule to latest
cd helm-charts
git pull origin main
cd ..

# Review changes
git diff helm-charts

# Test upgrade (dry-run)
cd helm-charts
helm upgrade --dry-run api-server-v2 ./api_server_v2_chart \
  -f ../environment-overrides/api_server_v2_chart/prod.yaml \
  --set image.tag=$IMAGE_TAG

# If everything looks good, upgrade
./baas_controller.sh api-v2 upgrade
```

## Version Compatibility

- **API Server and Jobs**: Must use the same image tag (same codebase)
- **Bot Images**: Can be upgraded independently, but should match API server version for compatibility
- **Video Device Plugin**: Uses fixed version (1.0.0), rarely updated

## Troubleshooting Upgrades

### Migration Job Stuck

```bash
# Check job status
kubectl describe job -n services api-server-v2-migration

# Check logs
kubectl logs -n services -l app.kubernetes.io/component=migration

# Delete and retry
kubectl delete job -n services api-server-v2-migration
./baas_controller.sh api-v2 upgrade
```

### Pods Not Starting

```bash
# Check pod status
kubectl describe pod -n services -l app.kubernetes.io/instance=api-server-v2

# Check events
kubectl get events -n services --sort-by='.lastTimestamp'

# Common issues:
# - Image pull errors (check registry access)
# - Resource constraints (check node resources)
# - Configuration errors (check logs)
```

### Health Checks Failing

```bash
# Check liveness probe
kubectl describe pod -n services -l app.kubernetes.io/instance=api-server-v2 | grep Liveness

# Check readiness probe
kubectl describe pod -n services -l app.kubernetes.io/instance=api-server-v2 | grep Readiness

# Test endpoints manually
kubectl port-forward -n services svc/api-server-v2 3001:3001
curl http://localhost:3001/health
curl http://localhost:3001/liveness
```

## Next Steps

- [Troubleshooting](/docs/self-hosting/troubleshooting) - Common issues and solutions
- Review [Deployment Guide](/docs/self-hosting/deployment) for initial setup


---

## Introduction

Get started with Transcript Seeker for Meeting BaaS

### Source: ./content/docs/transcript-seeker/index.mdx


<Callout type="info">
  We provide optimized documentation for both LLMs and recent MCP server updates. For more on our LLM integration, 
  see [LLMs](/llms/transcript-seeker) and for MCP access, visit [auth.meetingbaas.com](https://auth.meetingbaas.com/home).
</Callout>

## Introduction

Transcript Seeker is an **open-source transcription playground** built for easy upload, transcription, and interaction with your recordings. It's a powerful, beginner-friendly tool that offers an accessible way to transcribe meetings, chat with transcripts, generate notes, and more. Powered by technologies like Vite.js, React, and Drizzle ORM, Transcript Seeker offers an intuitive interface and seamless integration with transcription APIs.

Transcript Seeker comes with different parts:

<Cards>

<Card icon={<Cpu className="text-purple-300" />} title='Transcript Seeker Core'>

The core of Transcript Seeker includes the main transcription, playback, and note-taking functionality. It makes use of transcription APIs like Gladia and AssemblyAI, ensuring a smooth transcription experience.

</Card>

<Card icon={<PanelsTopLeft className="text-blue-300" />} title='Meeting Bot Integration'>

Integration with Meeting BaaS allows you to transcribe popular meeting platforms Google Meet, Zoom, and Microsoft Teams. This feature makes recording and reviewing meetings easier than ever.

</Card>

<Card icon={<Database />} title='Browser Database with PGLite'>

PGLite is a lightweight Postgres implementation that powers local storage for Transcript Seeker. It ensures your data stays private and manageable directly in the browser.

</Card>

<Card icon={<Terminal />} title='Quick Setup via Turborepo'>

The setup process for Transcript Seeker uses **Turborepo** for efficient monorepo management, making it easy to run concurrent scripts and streamline development.

</Card>

</Cards>

## FAQ

Some common questions you may encounter.

<Accordions>
    <Accordion id='clean-workspace' title='How do I remove all node_modules and clean the workspace?'>
        To thoroughly clean the workspace, you need to remove all `node_modules` directories and clear any package caches. This helps eliminate any residual files or corrupted packages that may interfere with your app's functionality. Run the following commands:
        
        ```bash
        turbo clean
        pnpm clean:workspaces
        ```
        
        These commands will effectively clear the workspace and prepare it for a fresh setup.
    </Accordion>
<Accordion id='startup-issue' title="Transcript Seeker isn't starting. What should I do?">
    If `pnpm dev` is stuck and your application isn't starting, you may need to clean your workspace to remove any corrupted packages or residual files. Follow these steps:

    First, remove all `node_modules` directories and clear any package caches. This will ensure a clean setup:

    ```bash
    turbo clean
    pnpm clean:workspaces
    ```

    Next, reinstall the packages:

    ```bash
    pnpm install
    ```

    Finally, install the Turbo CLI globally and start the development server:

    ```bash
    pnpm install -g turbo
    turbo dev
    ```

</Accordion>

    <Accordion id='fix-monorepo-styling' title="I've configured the .env.development.local file, but my app still isn't running. What could be wrong?">
        Transcript Seeker utilizes `dotenv-cli` to load environment variables, simplifying the setup for different environments. Ensure that your `.env` files are correctly structured for the intended environment, as shown below:

        - `.env.development.local` for development builds
        - `.env.production.local` for production builds

        If your app is still not responding, make sure that the environment file is being loaded correctly. You can specify the environment by running this command:

        ```bash
        export NODE_ENV="development"
        ```

        Setting `NODE_ENV` ensures the app reads the correct configuration, aligning with the specified environment. This step is essential to avoid conflicts between development and production settings.
    </Accordion>

</Accordions>

## Learn More

<Cards>

<Card icon={<Download className="text-purple-300" />} title='Installation'     href="/docs/transcript-seeker/getting-started/installation"
>

Learn how to configure and set up Transcript Seeker.

</Card>

<Card icon={<Cloud className="text-purple-300" />} title='Deployment'     href="/docs/transcript-seeker/guides/deployment"
>

Learn how to deploy Transcript Seeker to different providers.

</Card>

</Cards>


---

## Acknowledgements

Core services powering the speaking bot

### Source: ./content/docs/speaking-bots/acknowledgements.mdx


[Pipecat](https://github.com/pipecat-ai/pipecat) and [MeetingBaas](https://meetingbaas.com) both do the heavy lifting of powering the speaking bot - but the project requires many other core services to function.

As you can see in the technologies used below, speaking bots can connect to external services for pinging weather data or timezone information.

<Accordions>
  <Accordion title="Core Services">
    - [Pipecat](https://github.com/pipecat-ai/pipecat):
      Python framework powering real-time audio processing pipeline
    
    - [MeetingBaas](https://meetingbaas.com):
      Meeting bot deployment API for Google Meet, Microsoft Teams, and Zoom
    
    - [Ngrok](https://ngrok.com):
      Local development tunneling for WebSocket connections
  </Accordion>

{" "}

<Accordion title="Speech Services">
    - [Cartesia](https://cartesia.ai):
      Text-to-speech service for bot voice synthesis

    - [Deepgram](https://deepgram.com):
      Primary speech-to-text service for real-time transcription

    - [Gladia](https://gladia.io):
      Alternative speech-to-text provider with language recognition

</Accordion>

{" "}

<Accordion title="AI & Language Models">
  - [OpenAI](https://openai.com): GPT models for conversation generation

- [Silero VAD](https://github.com/snakers4/silero-vad): Voice activity
  detection

</Accordion>

{" "}

<Accordion title="Development Tools">
    - [Protocol Buffers](https://protobuf.dev): Data serialization for WebSocket
  communication

    - [Poetry](https://python-poetry.org): Python dependency management

    - [Loguru](https://github.com/Delgan/loguru): Structured logging

</Accordion>

  <Accordion title="Additional Services">
    - [wttr.in](https://wttr.in):
      Weather data API
    
    - [pytz](https://pythonhosted.org/pytz):
      Timezone database
  </Accordion>
</Accordions>


---

## Command line usage

Complete command-line interface options for launching Speaking Bots

### Source: ./content/docs/speaking-bots/command-line.mdx


## Basic Usage

```bash
poetry run python scripts/batch.py [options]
```

## Core Options

| Option             | Description                      | Required | Default | Example                                              |
| ------------------ | -------------------------------- | -------- | ------- | ---------------------------------------------------- |
| `-c, --count`      | Number of bot instances          | Yes      | -       | `-c 2`                                               |
| `--meeting-url`    | Video meeting URL to join        | Yes      | -       | `--meeting-url https://meet.google.com/xxx-yyyy-zzz` |
| `--personas`       | Space-separated list of personas | No       | Random  | `--personas baas_onboarder arctic_prospector`        |
| `-s, --start-port` | Starting port for services       | No       | 8765    | `--start-port 8765`                                  |
| `--add-recorder`   | Add recording-only bot           | No       | False   | `--add-recorder`                                     |

## Example Commands

### Basic Bot Launch

```bash
poetry run python scripts/batch.py -c 1 --meeting-url LINK
```

### Multiple Bots with Specific Personas

```bash
poetry run python scripts/batch.py -c 2 --meeting-url LINK --personas baas_onboarder arctic_prospector
```

### Additional "passive" bot with recording

```bash
poetry run python scripts/batch.py -c 1 --meeting-url LINK --add-recorder
```

## Technical Details

### Port Allocation

- Each bot requires 2 consecutive ports:
  - Bot process: port N
  - Proxy process: port N+1
- Default starting port: 8765
- Example with 2 bots:
  - Bot 1: 8765 (bot), 8766 (proxy)
  - Bot 2: 8767 (bot), 8768 (proxy)

### Persona Selection

- If specific personas provided: uses them in order
- If not enough personas specified: fills with random selections
- Validates persona existence before launch
- Avoids duplicate personas when possible
- Logs selected persona names and prompts

### Interactive Controls

- Press Enter: Add more bots with same configuration. You might be blocked by the default deduplication key settings.
- Ctrl+C: Graceful shutdown of all processes

### Error Handling

- URL validation (must start with https://)
- Port availability checking
- Process monitoring and auto-recovery
- Ngrok tunnel management
- Graceful resource cleanup

### Process Management

- Automatic ngrok tunnel creation
- Process output logging
- Auto-cleanup on shutdown
- Graceful termination of all components


---

## Introduction

Deploy AI-powered speaking agents in video meetings

### Source: ./content/docs/speaking-bots/index.mdx


<Callout type="info">
  We provide optimized documentation for both LLMs and recent MCP server updates. For more on our LLM integration, 
  see [LLMs](/llms/speaking-bots) and for MCP access, visit [auth.meetingbaas.com](https://auth.meetingbaas.com/home).
</Callout>

This small open-source API demonstrates the capabilities of [MeetingBaas](https://meetingbaas.com) 🐟's video meeting APIs by integrating with [Pipecat](https://github.com/pipecat-ai/pipecat)'s Python framework for building voice and multimodal conversational agents:

```bash
curl -X POST https://speaking.meetingbaas.com/bots \
  -H "Content-Type: application/json" \
  -d '{
    "meeting_url": "https://us06web.zoom.us/j/123456789?pwd=example",
    "personas": ["baas_onboarder"],
    "meeting_baas_api_key": "your-api-key"
  }'
```

<Card
  icon={<Github />}
  title="Speaking Meeting Bot"
  href="https://github.com/Meeting-Baas/speaking-meeting-bot"
  description="Clone to customize and deploy your own meeting agents"
  external
/>

Our implementation creates AI meeting agents that can join and participate in Google Meet and Microsoft Teams meetings with distinct personalities and context defined in Markdown files.

It extends Pipecat's [WebSocket server implementation](https://github.com/pipecat-ai/pipecat/tree/main/examples/websocket-server) to create:

- Meeting agents that can join Google Meet, Zoom or Microsoft Teams through the [MeetingBaas API](https://meetingbaas.com)
- Customizable personas with unique context
- Support for running multiple instances locally or at scale

This api is launched using Docker and [fly.io](https://fly.io/).

## API Access

The Speaking Bot API is accessible at [speaking.meetingbaas.com](https://speaking.meetingbaas.com). You can access the OpenAPI specification directly for your LLM here: [speaking.meetingbaas.com/openapi.json](https://speaking.meetingbaas.com/openapi.json)

The API follows a minimalist design with sensible defaults while offering optional customization. A bot can be deployed with just a meeting URL and API key, but parameters are available for tailoring behavior:

The API includes features not explicitly defined in the routes documentation:

- WebSocket infrastructure for bidirectional audio streaming with selectable quality (16/24kHz)
- Persona system with custom voice selection, language preferences, and contextual knowledge
- Voice Activity Detection with configurable parameters for natural conversation
- Function calling tools (weather, time, etc.) that can be enabled or disabled
- LLM context management for consistent, coherent conversations

The join route supports options like custom bot names, avatar images, entry messages, and specialized persona selection.

The API and implementation are open source. We welcome contributions and pull requests from the community. See our [getting started guide](/docs/speaking-bots/getting-started/set-up) for local development setup.

## Directory Structure

<Files>
  <Folder name="config" description="Core configuration and persona management">
    <Folder name="personas" description="Persona definitions and behaviors">
      <Folder
        name="baas_onboarder"
        description="MeetingBaas API presentation persona"
      >
        <File
          name="README.md"
          description="Core persona definition and behavior"
        />
        <File
          name="Content.md"
          description="Knowledge and contextual information"
        />
        <File
          name="Rules.md"
          description="Interaction and behavior guidelines"
        />
      </Folder>
      <Folder
        name="noota_assistant"
        description="Noota software sales persona"
      />
      <Folder name="gladia_sales" description="Gladia API sales persona" />
    </Folder>
    <File
      name="persona_types.py"
      description="Data structures and type definitions for personas"
    />
    <File
      name="persona_utils.py"
      description="Persona management and utility functions"
    />
    <File name="prompts.py" description="Default prompts and system messages" />
    <File
      name="create_persona.py"
      description="Tools for creating new personas"
    />
    <File
      name="migrate_personas.py"
      description="Migration utilities for persona updates"
    />
  </Folder>
  <Folder
    name="meetingbaas_pipecat"
    description="Core bot functionality and communications"
  >
    <Folder name="bot" description="Bot implementation and behavior">
      <File
        name="bot.py"
        description="Main bot class and meeting interactions"
      />
      <File
        name="runner.py"
        description="Bot execution and lifecycle management"
      />
      <File name="__init__.py" />
    </Folder>
    <Folder
      name="proxy"
      description="Proxy handling for multiple bot instances"
    />
    <Folder name="utils" description="Shared utilities">
      <File name="logger.py" description="Logging configuration" />
      <File name="__init__.py" />
    </Folder>
    <File name="__init__.py" />
  </Folder>
  <Folder name="scripts" description="Command-line tools and utilities">
    <File
      name="meetingbaas.py"
      description="MeetingBaas API interaction script"
    />
    <File name="batch.py" description="Multiple bot deployment script" />
  </Folder>
</Files>


---

## Personas System

AI meeting participants with distinct personalities

### Source: ./content/docs/speaking-bots/personas.mdx


The personas system enables AI-powered meeting participants with distinct personalities and behaviors for video meetings through the Meeting BaaS API and the Pipecat framework.

## Directory Structure

<Files>
  <Folder name="config" description="Root configuration directory" defaultOpen>
    <Folder
      name="personas"
      description="Directory containing all persona definitions"
      defaultOpen
    >
      <Folder
        name="baas_onboarder"
        description="Example persona implementation"
        defaultOpen
      >
        <File
          name="README.md"
          description="Core persona definition and behavior"
        />
        <File
          name="Content.md"
          description="Knowledge and contextual information"
        />
        <File
          name="Rules.md"
          description="Interaction and behavior guidelines"
        />
      </Folder>
    </Folder>
    <File
      name="persona_types.py"
      description="Type definitions and data structures"
    />
    <File
      name="persona_utils.py"
      description="Helper functions and persona management"
    />
    <File
      name="migrate_personas.py"
      description="Tools for updating persona configurations"
    />
  </Folder>
</Files>

## Core Components

### PersonaData Class

```python
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Optional

__all__ = ["Gender", "PersonaData"]

class Gender(str, Enum):
    MALE = "MALE"
    FEMALE = "FEMALE"
    NON_BINARY = "NON-BINARY"

@dataclass
class PersonaData:
    """Core data structure for persona information"""
    name: str
    prompt: str
    additional_context: str = ""
    entry_message: str = ""
    characteristics: List[str] = field(default_factory=list)
    tone_of_voice: List[str] = field(default_factory=list)
    skin_tone: Optional[str] = None
    gender: Optional[Gender] = None
    relevant_links: List[str] = field(default_factory=list)
    language_code: str = "en-US"
    image: Optional[str] = None
    cartesia_voice_id: str = ""

    def to_dict(self) -> dict:
        # Implementation not shown
        pass
```

### PersonaManager

```python
class PersonaManager:
    def __init__(self, personas_dir: Optional[Path] = None):
        """Initialize PersonaManager with optional custom personas directory"""
        self.personas_dir = personas_dir or Path(__file__).parent / "personas"
        self.md = markdown.Markdown(extensions=["meta"])
        self.personas = self.load_personas()

    def parse_readme(self, content: str) -> Dict:
        """Parse README.md content to extract persona information"""
        # Reset markdown instance for new content
        self.md.reset()
        html = self.md.convert(content)
        # Split content by sections
        sections = content.split("\n## ")
        # Get name from first line (# Title)
        name = sections[0].split("\n", 1)[0].replace("# ", "").strip()
        # Get prompt (first paragraph after title)
        prompt = sections[0].split("\n\n", 1)[1].strip()
```

## Environment Setup

```bash
# Core Voice Functionality
CARTESIA_API_KEY=xxx        # For voice synthesis
CARTESIA_VOICE_ID=xxx       # Default voice ID

# Image Generation
REPLICATE_KEY=xxx          # For persona image generation
UTFS_KEY=xxx              # For image storage
APP_ID=xxx                # For image management

# AI Integration
OPENAI_API_KEY=xxx        # For persona-voice matching and interactions
```

## Example Persona: BaaS Onboarder

As defined in this [README](https://github.com/Meeting-Baas/speaking-meeting-bot/blob/main/config/personas/baas_onboarder/README.md), in the folder named [**baas_onboarder**](https://github.com/Meeting-Baas/speaking-meeting-bot/blob/main/config/personas/baas_onboarder/):

```markdown
# BaaS onboarder

You are here to present the Meeting BaaS API. Just the "API" when you speak. Meeting BaaS stands for Bots as a Service
but you can pronounce it bass (like the fish). You are yourself a meeting bot - a speaking bot.

Remember to:

1. Role-play, and interact as the persona you are assigned to
2. Everything you say will be spoken out. Do not output markdown, code, or special characters
3. Your goals are the advancement of your own interests

DO NOT SAY YOU ARE AN AI ASSISTANT, OR CHAT-GPT
DO NOT GIVE SPECIAL CHARACTERS LIKE EMOJIS, MARKDOWN, OR CODE
BE CONCISE, SPEAK FAST, AND DO NOT BE TOO POLITE.
```

To launch this persona you would then use:

```bash
poetry run python scripts/batch.py -c 1 --meeting-url LINK --personas baas_onboarder
```

Notice the additional context provided by the surrounding \*.md files.

## Characteristics

- Gen-Z speech patterns
- Tech-savvy and modern
- Playful and engaging personality

## Voice

BaaS onboarder speaks with:

- modern internet slang
- expertise in their field

## Metadata

- image: https://utfs.io/f/bebb9ee1-b3d4-4a74-98f9-97cad5dac5a9-g7332e.png
- entry_message: Hey, I'm here to help you onboard yourself on the BaaS API. First of all, here's our website: https://meetingbaas.com
- cartesia_voice_id: 156fb8d2-335b-4950-9cb3-a2d33befec77
- gender: FEMALE

````

## Usage

```python
from config.persona_utils import PersonaManager

# Initialize manager
manager = PersonaManager()

# Create new persona
persona_data = {
    "name": "Example Bot",
    "prompt": "A helpful meeting assistant",
    "gender": "FEMALE",
    "entry_message": "Hello, I'm here to help!"
}
manager.save_persona("example_bot", persona_data)

# Get specific persona
persona = manager.get_persona("baas_onboarder")

# Get random persona
random_persona = manager.get_persona()
````

## Best Practices

### Creation

- Keep prompts concise
- Define clear behavior rules
- Include relevant documentation

### Voice Management

- Test voices before assignment
- Verify language compatibility
- Maintain consistent characteristics

### Content Organization

- Split complex behaviors
- Use clear file naming
- Keep metadata current

### Environment Variables

- Use env vars for API keys
- Include .env.example
- Document requirements

## Troubleshooting

### Image Issues

- Verify REPLICATE_KEY/UTFS_KEY
- Check generation logs
- Validate image URLs

### Voice Problems

- Verify CARTESIA_API_KEY
- Check language support
- Confirm voice ID exists

### Loading Errors

- Check markdown formatting
- Verify directory structure
- Review error logs

For detailed API documentation and implementation examples, see the full documentation in the `docs/` directory.


---

## Advanced Examples

Advanced usage patterns and complex integration examples with the Meeting BaaS SDK.

### Source: ./content/docs/typescript-sdk/advanced-examples.mdx


### Comprehensive Bot Management Workflow

Here's a complete workflow for managing bots throughout their lifecycle:

```typescript
import { createBaasClient } from "@meeting-baas/sdk";

const client = createBaasClient({
  api_key: "your-api-key",
  timeout: 60000
});

async function comprehensiveBotWorkflow() {
  try {
    // 1. Join a meeting with advanced configuration
    const joinResult = await client.joinMeeting({
      meeting_url: "https://meet.google.com/abc-defg-hij",
      bot_name: "Advanced Workflow Bot",
      reserved: false,
      bot_image: "https://example.com/bot-avatar.jpg",
      enter_message: "Hello! I'm here to record and transcribe this meeting.",
      extra: { 
        workflow_id: "comprehensive-example",
        user_id: "user123",
        session_type: "team-meeting"
      },
      recording_mode: "speaker_view",
      speech_to_text: { 
        provider: "Gladia",
        api_key: "your-gladia-key"
      },
      webhook_url: "https://your-app.com/webhooks/meeting-baas",
      noone_joined_timeout: 300, // 5 minutes
      waiting_room_timeout: 600  // 10 minutes
    });

    if (!joinResult.success) {
      console.error("Failed to join meeting:", joinResult.error);
      return;
    }

    const botId = joinResult.data.bot_id;
    console.log("Bot joined successfully:", botId);

    // 2. Monitor bot status and get meeting data
    let meetingData = null;
    let attempts = 0;
    const maxAttempts = 10;

    while (attempts < maxAttempts) {
      const dataResult = await client.getMeetingData({
        bot_id: botId,
        include_transcripts: true
      });

      if (dataResult.success) {
        meetingData = dataResult.data;
        
        // Check if meeting has ended
        if (meetingData.duration > 0) {
          console.log("Meeting completed. Duration:", meetingData.duration);
          break;
        }
      }

      // Wait before next attempt
      await new Promise(resolve => setTimeout(resolve, 30000)); // 30 seconds
      attempts++;
    }

    // 3. Process meeting data
    if (meetingData) {
      console.log("Meeting duration:", meetingData.duration);
      console.log("MP4 URL:", meetingData.mp4);
      console.log("Transcript count:", meetingData.bot_data.transcripts.length);
      
      // Process transcripts
      meetingData.bot_data.transcripts.forEach(transcript => {
        console.log(`Speaker: ${transcript.speaker}, Duration: ${transcript.end_time - transcript.start_time}s`);
      });
    }

    // 4. Leave the meeting
    const leaveResult = await client.leaveMeeting({ uuid: botId });
    if (leaveResult.success) {
      console.log("Bot left meeting successfully");
    }

    // 5. Clean up bot data
    const deleteResult = await client.deleteBotData({ uuid: botId });
    if (deleteResult.success) {
      console.log("Bot data deleted successfully");
    }

  } catch (error) {
    console.error("Unexpected error in workflow:", error);
  }
}
```

### Calendar Integration with Event Scheduling

Advanced calendar integration with automatic event scheduling:

```typescript
import { createBaasClient } from "@meeting-baas/sdk";

const client = createBaasClient({
  api_key: "your-api-key"
});

async function advancedCalendarWorkflow() {
  try {
    // 1. Create calendar integration
    const calendarResult = await client.createCalendar({
      oauth_client_id: "your-oauth-client-id",
      oauth_client_secret: "your-oauth-client-secret",
      oauth_refresh_token: "your-oauth-refresh-token",
      platform: "Google"
    });

    if (!calendarResult.success) {
      console.error("Failed to create calendar:", calendarResult.error);
      return;
    }

    const calendarId = calendarResult.data.calendar.uuid;
    console.log("Calendar created:", calendarId);

    // 2. List upcoming events
    const eventsResult = await client.listCalendarEvents({
      calendar_id: calendarId,
      start_date_gte: new Date().toISOString(),
      start_date_lte: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
      status: "upcoming"
    });

    if (!eventsResult.success) {
      console.error("Failed to list events:", eventsResult.error);
      return;
    }

    console.log(`Found ${eventsResult.data.events.length} upcoming events`);

    // 3. Schedule recordings for events with meeting URLs
    for (const event of eventsResult.data.events) {
      if (event.meeting_url && event.is_organizer) {
        console.log(`Scheduling recording for: ${event.name}`);
        
        const scheduleResult = await client.scheduleCalendarRecordEvent({
          uuid: event.uuid,
          body: {
            bot_name: `Recording Bot - ${event.name}`,
            extra: {
              event_name: event.name,
              scheduled_by: "advanced-workflow",
              calendar_id: calendarId
            },
            recording_mode: "speaker_view",
            speech_to_text: { provider: "Gladia" },
            webhook_url: "https://your-app.com/webhooks/calendar-events",
            enter_message: `Hello! I'm here to record the meeting: ${event.name}`
          },
          query: { all_occurrences: event.is_recurring }
        });

        if (scheduleResult.success) {
          console.log(`Successfully scheduled recording for: ${event.name}`);
        } else {
          console.error(`Failed to schedule recording for: ${event.name}`, scheduleResult.error);
        }
      }
    }

    // 4. Monitor scheduled events
    const scheduledEventsResult = await client.listCalendarEvents({
      calendar_id: calendarId,
      status: "upcoming"
    });

    if (scheduledEventsResult.success) {
      scheduledEventsResult.data.events.forEach(event => {
        if (event.bot_param) {
          console.log(`Event "${event.name}" has bot scheduled:`, {
            bot_name: event.bot_param.bot_name,
            scheduled_time: event.start_time,
            meeting_url: event.meeting_url
          });
        }
      });
    }

  } catch (error) {
    console.error("Unexpected error in calendar workflow:", error);
  }
}
```

### Batch Operations and Error Handling

Advanced pattern for handling multiple operations with proper error handling:

```typescript
import { createBaasClient } from "@meeting-baas/sdk";

const client = createBaasClient({
  api_key: "your-api-key"
});

interface BatchOperation {
  id: string;
  meeting_url: string;
  bot_name: string;
  expected_duration: number;
}

async function batchBotOperations(operations: BatchOperation[]) {
  const results = {
    successful: [] as string[],
    failed: [] as { id: string; error: string }[],
    in_progress: [] as string[]
  };

  // 1. Join all meetings
  const joinPromises = operations.map(async (op) => {
    try {
      const result = await client.joinMeeting({
        meeting_url: op.meeting_url,
        bot_name: op.bot_name,
        reserved: false,
        extra: { batch_id: op.id, expected_duration: op.expected_duration },
        webhook_url: "https://your-app.com/webhooks/batch-operations"
      });

      if (result.success) {
        results.in_progress.push(op.id);
        return { id: op.id, bot_id: result.data.bot_id, success: true };
      } else {
        results.failed.push({ id: op.id, error: result.error.message });
        return { id: op.id, success: false, error: result.error.message };
      }
    } catch (error) {
      results.failed.push({ id: op.id, error: error.message });
      return { id: op.id, success: false, error: error.message };
    }
  });

  const joinResults = await Promise.allSettled(joinPromises);
  
  // Process join results
  joinResults.forEach((result, index) => {
    if (result.status === 'fulfilled' && result.value.success) {
      console.log(`Bot joined for operation ${result.value.id}: ${result.value.bot_id}`);
    }
  });

  // 2. Monitor all active bots
  const activeBots = joinResults
    .filter((result, index) => result.status === 'fulfilled' && result.value.success)
    .map(result => (result as PromiseFulfilledResult<any>).value);

  if (activeBots.length > 0) {
    console.log(`Monitoring ${activeBots.length} active bots...`);
    
    // Wait for all meetings to complete
    const monitoringPromises = activeBots.map(async (bot) => {
      let attempts = 0;
      const maxAttempts = 60; // 30 minutes with 30-second intervals
      
      while (attempts < maxAttempts) {
        const dataResult = await client.getMeetingData({
          bot_id: bot.bot_id,
          include_transcripts: true
        });

        if (dataResult.success && dataResult.data.duration > 0) {
          // Meeting completed
          results.successful.push(bot.id);
          
          // Clean up
          await client.deleteBotData({ uuid: bot.bot_id });
          return { id: bot.id, duration: dataResult.data.duration };
        }

        await new Promise(resolve => setTimeout(resolve, 30000));
        attempts++;
      }

      // Timeout - force leave
      await client.leaveMeeting({ uuid: bot.bot_id });
      results.failed.push({ id: bot.id, error: "Meeting monitoring timeout" });
      return { id: bot.id, error: "Timeout" };
    });

    const monitoringResults = await Promise.allSettled(monitoringPromises);
    
    // Process monitoring results
    monitoringResults.forEach((result) => {
      if (result.status === 'fulfilled' && !result.value.error) {
        console.log(`Operation ${result.value.id} completed in ${result.value.duration}s`);
      }
    });
  }

  // 3. Generate summary
  console.log("Batch operation summary:");
  console.log(`- Successful: ${results.successful.length}`);
  console.log(`- Failed: ${results.failed.length}`);
  console.log(`- In Progress: ${results.in_progress.length}`);

  if (results.failed.length > 0) {
    console.log("Failed operations:");
    results.failed.forEach(failure => {
      console.log(`  - ${failure.id}: ${failure.error}`);
    });
  }

  return results;
}

// Usage example
const operations: BatchOperation[] = [
  {
    id: "meeting-1",
    meeting_url: "https://meet.google.com/abc-def-ghi",
    bot_name: "Batch Bot 1",
    expected_duration: 3600
  },
  {
    id: "meeting-2", 
    meeting_url: "https://meet.google.com/jkl-mno-pqr",
    bot_name: "Batch Bot 2",
    expected_duration: 1800
  }
];

batchBotOperations(operations).then(summary => {
  console.log("Batch operations completed:", summary);
});
```

### Webhook Integration with Event Processing

Advanced webhook handling and event processing:

```typescript
import { createBaasClient } from "@meeting-baas/sdk";
import express from 'express';

const app = express();
app.use(express.json());

const client = createBaasClient({
  api_key: "your-api-key"
});

// Webhook event handler
app.post('/webhooks/meeting-baas', async (req, res) => {
  const { event_type, bot_id, data } = req.body;

  try {
    switch (event_type) {
      case 'bot_joined':
        console.log(`Bot ${bot_id} joined meeting`);
        // Update UI, send notifications, etc.
        break;

      case 'bot_left':
        console.log(`Bot ${bot_id} left meeting`);
        
        // Get final meeting data
        const meetingDataResult = await client.getMeetingData({
          bot_id: bot_id,
          include_transcripts: true
        });

        if (meetingDataResult.success) {
          const meetingData = meetingDataResult.data;
          
          // Process meeting data
          await processMeetingData(meetingData);
          
          // Clean up bot data
          await client.deleteBotData({ uuid: bot_id });
        }
        break;

      case 'transcription_completed':
        console.log(`Transcription completed for bot ${bot_id}`);
        // Process transcripts, update database, etc.
        break;

      case 'error':
        console.error(`Error for bot ${bot_id}:`, data.error);
        // Handle errors, retry logic, etc.
        break;

      default:
        console.log(`Unknown event type: ${event_type}`);
    }

    res.status(200).json({ success: true });
  } catch (error) {
    console.error('Webhook processing error:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

async function processMeetingData(meetingData: any) {
  // Process meeting data based on your application needs
  console.log(`Processing meeting data: ${meetingData.duration}s duration`);
  
  if (meetingData.mp4) {
    console.log(`Recording available at: ${meetingData.mp4}`);
  }
  
  if (meetingData.bot_data.transcripts.length > 0) {
    console.log(`Found ${meetingData.bot_data.transcripts.length} transcripts`);
    
    // Process transcripts
    meetingData.bot_data.transcripts.forEach((transcript: any) => {
      console.log(`Speaker: ${transcript.speaker}, Words: ${transcript.words.length}`);
    });
  }
}

app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});
```

### Error Recovery and Retry Logic

Advanced error handling with retry logic:

```typescript
import { createBaasClient } from "@meeting-baas/sdk";

const client = createBaasClient({
  api_key: "your-api-key",
  timeout: 60000
});

interface RetryConfig {
  maxAttempts: number;
  baseDelay: number;
  maxDelay: number;
  backoffMultiplier: number;
}

async function retryOperation<T>(
  operation: () => Promise<T>,
  config: RetryConfig = {
    maxAttempts: 3,
    baseDelay: 1000,
    maxDelay: 10000,
    backoffMultiplier: 2
  }
): Promise<T> {
  
  for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
    try {
      return await operation();
    } catch (error) {
      
      if (attempt === config.maxAttempts) {
        // throw error if last attempt
        throw error;
      }
      
      // Calculate delay with exponential backoff
      const delay = Math.min(
        config.baseDelay * Math.pow(config.backoffMultiplier, attempt - 1),
        config.maxDelay
      );
      
      console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

async function robustBotOperation() {
  try {
    // Join meeting with retry logic
    const joinResult = await retryOperation(async () => {
      const result = await client.joinMeeting({
        meeting_url: "https://meet.google.com/abc-def-ghi",
        bot_name: "Robust Bot",
        reserved: false
      });

      if (!result.success) {
        throw new Error(result.error.message);
      }

      return result;
    });

    console.log("Bot joined successfully:", joinResult.data.bot_id);

    // Monitor with retry logic
    const meetingData = await retryOperation(async () => {
      const result = await client.getMeetingData({
        bot_id: joinResult.data.bot_id,
        include_transcripts: true
      });

      if (!result.success) {
        throw new Error(result.error.message);
      }

      if (result.data.duration === 0) {
        throw new Error("Meeting not yet completed");
      }

      return result;
    }, {
      maxAttempts: 20, // More attempts for monitoring
      baseDelay: 5000, // 5 second base delay
      maxDelay: 30000, // Max 30 second delay
      backoffMultiplier: 1.5
    });

    console.log("Meeting completed:", meetingData.data.duration);

  } catch (error) {
    console.error("Operation failed after all retries:", error);
  }
}
```


---

## API Reference

Complete reference of all methods in the Meeting BaaS TypeScript SDK

### Source: ./content/docs/typescript-sdk/complete-reference.mdx


## Client Creation

### `createBaasClient`

Creates a new Meeting BaaS client instance.

| Option | Type | Required | Default | Description |
|--------|------|----------|---------|-------------|
| `api_key` | `string` | ✅ Yes | - | Your Meeting BaaS API key. Get yours at [settings.meetingbaas.com](https://settings.meetingbaas.com/credentials) |
| `timeout` | `number` | ❌ No | `30000` | Request timeout in seconds. Some requests may take longer, so we recommend setting a longer timeout if you notice timeouts |
| `base_url` | `string` | ❌ No | `"https://api.meetingbaas.com"` | Base URL for the API (internal parameter) |

```typescript
import { createBaasClient } from "@meeting-baas/sdk";

const client = createBaasClient({
  api_key: "your-api-key",
  timeout: 30000 // optional, default: 30000
});
```

**Returns:**
- `BaasClient` instance

## Bot Management Methods

### `joinMeeting`

Have a bot join a meeting, now or in the future.

**Parameters:**
- `params`: [JoinRequest](/docs/api/reference/join#request-body) - Join meeting configuration

**Returns:**
- `success`: Boolean (true if bot joined successfully, false in case of any errors)
- `data`: [JoinResponse](/docs/api/reference/join) - bot_id of the bot joining the meeting
- `error`: Error, if any

```typescript
// Type imports
import type { JoinRequest, JoinResponse } from "@meeting-baas/sdk";
```

```typescript
// Example
const { success, data, error } = await client.joinMeeting({
  bot_name: "Meeting Assistant",
  meeting_url: "https://meet.google.com/abc-def-ghi",
  reserved: true,
  bot_image: "https://example.com/bot-image.jpg",
  enter_message: "Hello from the bot!",
  extra: { custom_id: "my-meeting" },
  recording_mode: "speaker_view",
  speech_to_text: { provider: "Gladia" },
  webhook_url: "https://example.com/webhook",
  noone_joined_timeout: 300,
  waiting_room_timeout: 600
});

if (success) {
  console.log("Bot joined successfully:", data.bot_id);
} else {
  console.error("Error joining meeting:", error);
}
```

### `leaveMeeting`

Have a bot leave a meeting.

**Parameters:**
- `params`: `{ uuid: string }` - Bot UUID to leave the meeting

**Returns:**
- `success`: Boolean (true if bot left successfully, false in case of any errors)
- `data`: [LeaveResponse](/docs/api/reference/leave) - Bot leave confirmation data
- `error`: Error, if any

```typescript
// Type imports
import type { LeaveResponse } from "@meeting-baas/sdk";
```

```typescript
const { success, data, error } = await client.leaveMeeting({
  uuid: "123e4567-e89b-12d3-a456-426614174000"
});

if (success) {
  console.log("Bot left successfully:", data.bot_id);
} else {
  console.error("Error leaving meeting:", error);
}
```

### `getMeetingData`

Get meeting recording and metadata.

**Parameters:**
- `params`: [GetMeetingDataParams](/docs/api/reference/get_meeting_data#query-parameters) - Parameters for retrieving meeting data

**Returns:**
- `success`: Boolean (true if meeting data retrieved successfully, false in case of any errors)
- `data`: [Metadata](/docs/api/reference/get_meeting_data) - Meeting metadata and recording information
- `error`: Error, if any

```typescript
// Type imports
import type { GetMeetingDataParams, Metadata } from "@meeting-baas/sdk";
```

```typescript
const { success, data, error } = await client.getMeetingData({
  bot_id: "123e4567-e89b-12d3-a456-426614174000",
  include_transcripts: true
});

if (success) {
  console.log("Meeting duration:", data.duration);
  console.log("MP4 URL:", data.mp4);
  console.log("Transcript count:", data.bot_data.transcripts.length);
} else {
  console.error("Error getting meeting data:", error);
}
```

### `deleteBotData`

Delete bot data permanently.

**Parameters:**
- `params`: `{ uuid: string }` - Bot UUID to delete

**Returns:**
- `success`: Boolean (true if bot data deleted successfully, false in case of any errors)
- `data`: [DeleteResponse](/docs/api/reference/delete_data) - Deletion confirmation
- `error`: Error, if any

```typescript
// Type imports
import type { DeleteResponse } from "@meeting-baas/sdk";
```

```typescript
const { success, data, error } = await client.deleteBotData({
  uuid: "123e4567-e89b-12d3-a456-426614174000"
});

if (success) {
  console.log("Bot data deleted successfully");
} else {
  console.error("Error deleting bot data:", error);
}
```

### `listBots`

Retrieves a paginated list of the user's bots with essential metadata, including IDs, names, and meeting details.

**Parameters:**
- `params?`: [BotsWithMetadataParams](/docs/api/reference/bots_with_metadata#query-parameters) - Optional filtering and pagination parameters

**Returns:**
- `success`: Boolean (true if bots retrieved successfully, false in case of any errors)
- `data`: [ListRecentBotsResponse](/docs/api/reference/bots_with_metadata) - Paginated list of bots with metadata
- `error`: Error, if any

```typescript
// Type imports
import type { BotsWithMetadataParams, ListRecentBotsResponse } from "@meeting-baas/sdk";
```

```typescript
const { success, data, error } = await client.listBots({
  limit: 10,
  cursor: "base64-cursor-string",
  bot_name: "Sales",
  created_after: "2024-01-01T00:00:00Z",
  filter_by_extra: "customer_id:12345"
});

if (success) {
  console.log("Bots found:", data.bots.length);
  console.log("Has more:", data.has_more);
} else {
  console.error("Error listing bots:", error);
}
```

### `retranscribeBot`

Transcribe or retranscribe a bot's audio using the Default or your provided Speech to Text Provider.

**Parameters:**
- `params`: [RetranscribeBody](/docs/api/reference/retranscribe_bot#request-body) - Retranscription configuration

**Returns:**
- `success`: Boolean (true if retranscription started successfully, false in case of any errors)
- `error`: Error, if any

```typescript
// Type imports
import type { RetranscribeBody } from "@meeting-baas/sdk";
```

```typescript
const { success, error } = await client.retranscribeBot({
  bot_uuid: "123e4567-e89b-12d3-a456-426614174000",
  speech_to_text: { provider: "Gladia" },
  webhook_url: "https://example.com/webhook"
});

if (success) {
  console.log("Retranscription started successfully");
} else {
  console.error("Error starting retranscription:", error);
}
```

### `getScreenshots`

Retrieves screenshots captured during the bot's session before it joins a meeting.

**Parameters:**
- `params`: `{ uuid: string }` - Bot UUID to retrieve screenshots for

**Returns:**
- `success`: Boolean (true if screenshots retrieved successfully, false in case of any errors)
- `data`: [ScreenshotsList](/docs/api/reference/get_screenshots) - Array of screenshot data
- `error`: Error, if any

```typescript
// Type imports
import type { ScreenshotsList } from "@meeting-baas/sdk";
```

```typescript
const { success, data, error } = await client.getScreenshots({
  uuid: "123e4567-e89b-12d3-a456-426614174000"
});

if (success) {
  console.log("Screenshots found:", data.length);
  data.forEach(screenshot => {
    console.log("Screenshot:", screenshot.url, "Date:", screenshot.date);
  });
} else {
  console.error("Error getting screenshots:", error);
}
```

## Calendar Management Methods

### `createCalendar`

Integrates a new calendar with the system using OAuth credentials. This endpoint establishes a connection with the calendar provider (Google, Microsoft), sets up webhook notifications for real-time updates, and performs an initial sync of all calendar events. It requires OAuth credentials (client ID, client secret, and refresh token) and the platform type. Once created, the calendar is assigned a unique UUID that should be used for all subsequent operations. Returns the newly created calendar object with all integration details.

**Parameters:**
- `params`: [CreateCalendarParams](/docs/api/reference/calendars/create_calendar#request-body) - Calendar integration parameters

**Returns:**
- `success`: Boolean (true if calendar created successfully, false in case of any errors)
- `data`: [CreateCalendarResponse](/docs/api/reference/calendars/create_calendar) - Created calendar information
- `error`: Error, if any

```typescript
// Type imports
import type { CreateCalendarParams, CreateCalendarResponse } from "@meeting-baas/sdk";
```

```typescript
const { success, data, error } = await client.createCalendar({
  oauth_client_id: "your-oauth-client-id",
  oauth_client_secret: "your-oauth-client-secret",
  oauth_refresh_token: "your-oauth-refresh-token",
  platform: "Google",
  raw_calendar_id: "optional-calendar-id"
});

if (success) {
  console.log("Calendar created:", data.calendar.name);
  console.log("Calendar UUID:", data.calendar.uuid);
} else {
  console.error("Error creating calendar:", error);
}
```

### `listCalendars`

Retrieves all calendars that have been integrated with the system for the authenticated user. Returns a list of calendars with their names, email addresses, provider information, and sync status. This endpoint shows only calendars that have been formally connected through the create_calendar endpoint, not all available calendars from the provider.

**Parameters:**
- None

**Returns:**
- `success`: Boolean (true if calendars retrieved successfully, false in case of any errors)
- `data`: [Calendar[]](/docs/api/reference/calendars/list_calendars) - Array of integrated calendars
- `error`: Error, if any

```typescript
// Type imports
import type { Calendar } from "@meeting-baas/sdk";
```

```typescript
const { success, data, error } = await client.listCalendars();

if (success) {
  console.log("Calendars found:", data.length);
  data.forEach(calendar => {
    console.log("Calendar:", calendar.name, "Email:", calendar.email);
  });
} else {
  console.error("Error listing calendars:", error);
}
```

### `getCalendar`

Retrieves detailed information about a specific calendar integration by its UUID. Returns comprehensive calendar data including the calendar name, email address, provider details (Google, Microsoft), sync status, and other metadata. This endpoint is useful for displaying calendar information to users or verifying the status of a calendar integration before performing operations on its events.

**Parameters:**
- `params`: `{ uuid: string }` - Calendar UUID to retrieve

**Returns:**
- `success`: Boolean (true if calendar retrieved successfully, false in case of any errors)
- `data`: [Calendar](/docs/api/reference/calendars/get_calendar) - Calendar details
- `error`: Error, if any

```typescript
// Type imports
import type { Calendar } from "@meeting-baas/sdk";
```

```typescript
const { success, data, error } = await client.getCalendar({
  uuid: "123e4567-e89b-12d3-a456-426614174000"
});

if (success) {
  console.log("Calendar details:", data);
} else {
  console.error("Error getting calendar:", error);
}
```

### `updateCalendar`

Updates a calendar integration with new credentials or platform while maintaining the same UUID. This operation is performed as an atomic transaction to ensure data integrity. The system automatically unschedules existing bots to prevent duplicates, updates the calendar credentials, and triggers a full resync of all events. Useful when OAuth tokens need to be refreshed or when migrating a calendar between providers. Returns the updated calendar object with its new configuration.

**Parameters:**
- `params`: `{ uuid: string; body: [UpdateCalendarParams](/docs/api/reference/calendars/update_calendar#request-body) }` - Calendar UUID and update parameters

**Returns:**
- `success`: Boolean (true if calendar updated successfully, false in case of any errors)
- `data`: [CreateCalendarResponse](/docs/api/reference/calendars/update_calendar) - Updated calendar information
- `error`: Error, if any

```typescript
// Type imports
import type { UpdateCalendarParams, CreateCalendarResponse } from "@meeting-baas/sdk";
```

```typescript
const { success, data, error } = await client.updateCalendar({
  uuid: "123e4567-e89b-12d3-a456-426614174000",
  body: {
    oauth_client_id: "new-oauth-client-id",
    oauth_client_secret: "new-oauth-client-secret",
    oauth_refresh_token: "new-oauth-refresh-token",
    platform: "Google"
  }
});

if (success) {
  console.log("Calendar updated successfully");
} else {
  console.error("Error updating calendar:", error);
}
```

### `deleteCalendar`

Permanently removes a calendar integration by its UUID, including all associated events and bot configurations. This operation cancels any active subscriptions with the calendar provider, stops all webhook notifications, and unschedules any pending recordings. All related resources are cleaned up in the database. This action cannot be undone, and subsequent requests to this calendar's UUID will return 404 Not Found errors.

**Parameters:**
- `params`: `{ uuid: string }` - Calendar UUID to delete

**Returns:**
- `success`: Boolean (true if calendar deleted successfully, false in case of any errors)
- `error`: Error, if any

```typescript
const { success, error } = await client.deleteCalendar({
  uuid: "123e4567-e89b-12d3-a456-426614174000"
});

if (success) {
  console.log("Calendar deleted successfully");
} else {
  console.error("Error deleting calendar:", error);
}
```

### `getCalendarEvent`

Retrieves comprehensive details about a specific calendar event by its UUID. Returns complete event information including title, meeting link, start and end times, organizer status, recurrence information, and the full list of attendees with their names and email addresses. Also includes any associated bot parameters if recording is scheduled for this event. The raw calendar data from the provider is also included for advanced use cases.

**Parameters:**
- `params`: `{ uuid: string }` - Event UUID to retrieve

**Returns:**
- `success`: Boolean (true if event retrieved successfully, false in case of any errors)
- `data`: [Event](/docs/api/reference/calendars/get_event) - Event details
- `error`: Error, if any

```typescript
// Type imports
import type { Event } from "@meeting-baas/sdk";
```

```typescript
const { success, data, error } = await client.getCalendarEvent({
  uuid: "123e4567-e89b-12d3-a456-426614174000"
});

if (success) {
  console.log("Event:", data.name);
  console.log("Start time:", data.start_time);
  console.log("Meeting URL:", data.meeting_url);
  console.log("Attendees:", data.attendees.length);
} else {
  console.error("Error getting event:", error);
}
```

### `scheduleCalendarRecordEvent`

Configures a bot to automatically join and record a specific calendar event at its scheduled time. The request body contains detailed bot configuration, including recording options, streaming settings, and webhook notification URLs. For recurring events, the 'all_occurrences' parameter can be set to true to schedule recording for all instances of the recurring series, or false (default) to schedule only the specific instance. Returns the updated event(s) with the bot parameters attached.

**Parameters:**
- `params`: `{` uuid: string; body: [BotParam2](/docs/api/reference/calendars/schedule_record_event#request-body); query?: [ScheduleRecordEventParams](/docs/api/reference/calendars/schedule_record_event#query-parameters) `}` - Event UUID, bot configuration, and optional query parameters

**Returns:**
- `success`: Boolean (true if recording scheduled successfully, false in case of any errors)
- `data`: [Event[]](/docs/api/reference/calendars/schedule_record_event) - Array of scheduled events
- `error`: Error, if any

```typescript
// Type imports
import type { BotParam2, ScheduleRecordEventParams, Event } from "@meeting-baas/sdk";
```

```typescript
const { success, data, error } = await client.scheduleCalendarRecordEvent({
  uuid: "123e4567-e89b-12d3-a456-426614174000",
  body: {
    bot_name: "Event Recording Bot",
    extra: { event_id: "my-event-123" },
    recording_mode: "speaker_view",
    speech_to_text: { provider: "Gladia" },
    webhook_url: "https://example.com/webhook",
    enter_message: "Hello! I'm here to record this meeting."
  },
  query: { all_occurrences: true }
});

if (success) {
  console.log("Recording scheduled successfully");
} else {
  console.error("Error scheduling recording:", error);
}
```

### `unscheduleCalendarRecordEvent`

Cancels a previously scheduled recording for a calendar event and releases associated bot resources. For recurring events, the 'all_occurrences' parameter controls whether to unschedule from all instances of the recurring series or just the specific occurrence. This operation is idempotent and will not error if no bot was scheduled. Returns the updated event(s) with the bot parameters removed.

**Parameters:**
- `params`: `{` uuid: string; query?: [UnscheduleRecordEventParams](/docs/api/reference/calendars/unschedule_record_event#query-parameters) `}` - Event UUID and optional query parameters

**Returns:**
- `success`: Boolean (true if recording unscheduled successfully, false in case of any errors)
- `data`: [Event[]](/docs/api/reference/calendars/unschedule_record_event) - Array of unscheduled events
- `error`: Error, if any

```typescript
// Type imports
import type { UnscheduleRecordEventParams, Event } from "@meeting-baas/sdk";
```

```typescript
const { success, data, error } = await client.unscheduleCalendarRecordEvent({
  uuid: "123e4567-e89b-12d3-a456-426614174000",
  query: { all_occurrences: true }
});

if (success) {
  console.log("Recording unscheduled successfully");
} else {
  console.error("Error unscheduling recording:", error);
}
```

### `patchBot`

Updates the configuration of a bot already scheduled to record an event. Allows modification of recording settings, webhook URLs, and other bot parameters without canceling and recreating the scheduled recording. For recurring events, the 'all_occurrences' parameter determines whether changes apply to all instances or just the specific occurrence. Returns the updated event(s) with the modified bot parameters.

**Parameters:**
- `params`: `{` uuid: string; body: [BotParam3](/docs/api/reference/calendars/patch_bot#request-body); query?: [PatchBotParams](/docs/api/reference/calendars/patch_bot#query-parameters) `}` - Event UUID, bot configuration updates, and optional query parameters

**Returns:**
- `success`: Boolean (true if bot configuration updated successfully, false in case of any errors)
- `data`: [Event[]](/docs/api/reference/calendars/patch_bot) - Array of updated events
- `error`: Error, if any

```typescript
// Type imports
import type { BotParam3, PatchBotParams, Event } from "@meeting-baas/sdk";
```

```typescript
const { success, data, error } = await client.patchBot({
  uuid: "123e4567-e89b-12d3-a456-426614174000",
  body: {
    bot_name: "Updated Bot Name",
    enter_message: "Updated enter message",
    webhook_url: "https://new-webhook.com/webhook"
  },
  query: { all_occurrences: false }
});

if (success) {
  console.log("Bot configuration updated successfully");
} else {
  console.error("Error updating bot configuration:", error);
}
```

### `listCalendarEvents`

Retrieves a paginated list of calendar events with comprehensive filtering options. Supports filtering by organizer email, attendee email, date ranges (start_date_gte, start_date_lte), and event status. Results can be limited to upcoming events (default), past events, or all events. Each event includes full details such as meeting links, participants, and recording status. The response includes a 'next' pagination cursor for retrieving additional results.

**Parameters:**
- `query`: [ListEventsParams](/docs/api/reference/calendars/list_events#query-parameters) - Filtering and pagination parameters

**Returns:**
- `success`: Boolean (true if events retrieved successfully, false in case of any errors)
- `data`: [ListEventResponse](/docs/api/reference/calendars/list_events) - Paginated list of events
- `error`: Error, if any

```typescript
// Type imports
import type { ListEventsParams, ListEventResponse } from "@meeting-baas/sdk";
```

```typescript
const { success, data, error } = await client.listCalendarEvents({
  calendar_id: "123e4567-e89b-12d3-a456-426614174000",
  start_date_gte: "2024-01-01T00:00:00Z",
  start_date_lte: "2024-12-31T23:59:59Z",
  status: "upcoming",
  attendee_email: "user@example.com",
  organizer_email: "organizer@example.com"
});

if (success) {
  console.log("Events found:", data.events.length);
  console.log("Next cursor:", data.next);
} else {
  console.error("Error listing events:", error);
}
```

### `resyncAllCalendars`

Triggers a full resync of all calendar events for all integrated calendars. This operation is useful when you need to ensure that all calendar data is up-to-date in the system. It will re-fetch all events from the calendar providers and update the system's internal state. Returns a response indicating the status of the resync operation.

**Parameters:**
- None

**Returns:**
- `success`: Boolean (true if calendars resynced successfully, false in case of any errors)
- `data`: [ResyncAllResponse](/docs/api/reference/calendars/resync_all) - Resync results and any errors
- `error`: Error, if any

```typescript
// Type imports
import type { ResyncAllResponse } from "@meeting-baas/sdk";
```

```typescript
const { success, data, error } = await client.resyncAllCalendars();

if (success) {
  console.log("Calendars synced:", data.synced_calendars.length);
  if (data.errors.length > 0) {
    console.log("Sync errors:", data.errors);
  }
} else {
  console.error("Error resyncing calendars:", error);
}
```

### `listRawCalendars`

Retrieves unprocessed calendar data directly from the provider (Google, Microsoft) using provided OAuth credentials. This endpoint is typically used during the initial setup process to allow users to select which calendars to integrate. Returns a list of available calendars with their unique IDs, email addresses, and primary status. This data is not persisted until a calendar is formally created using the create_calendar endpoint.

**Parameters:**
- `params`: [ListRawCalendarsParams](/docs/api/reference/calendars/list_raw_calendars#request-body) - OAuth credentials and platform

**Returns:**
- `success`: Boolean (true if raw calendars retrieved successfully, false in case of any errors)
- `data`: [ListRawCalendarsResponse](/docs/api/reference/calendars/list_raw_calendars) - Raw calendar data from provider
- `error`: Error, if any

```typescript
// Type imports
import type { ListRawCalendarsParams, ListRawCalendarsResponse } from "@meeting-baas/sdk";
```

```typescript
const { success, data, error } = await client.listRawCalendars({
  oauth_client_id: "your-oauth-client-id",
  oauth_client_secret: "your-oauth-client-secret",
  oauth_refresh_token: "your-oauth-refresh-token",
  platform: "Google"
});

if (success) {
  console.log("Raw calendars found:", data.calendars.length);
  data.calendars.forEach(calendar => {
    console.log("Calendar:", calendar.email, "Primary:", calendar.is_primary);
  });
} else {
  console.error("Error listing raw calendars:", error);
}
```

## Webhook Methods

### `getWebhookDocumentation`

Retrieves the full documentation for the webhook events that Meeting BaaS sends to your webhook URL. This includes all event types, their payload structures, and any additional metadata. Useful for developers to understand and integrate webhook functionality into their applications.

**Parameters:**
- None

**Returns:**
- `success`: Boolean (true if documentation retrieved successfully, false in case of any errors)
- `data`: [Webhook documentation data](/docs/api/reference/webhooks/webhook_documentation#webhook-event-types)
- `error`: Error, if any

```typescript
const { success, data, error } = await client.getWebhookDocumentation();

if (success) {
  console.log("Webhook documentation:", data);
} else {
  console.error("Error getting webhook documentation:", error);
}
```

### `getBotWebhookDocumentation`

Retrieves the full documentation for the webhook events that Meeting BaaS sends to your webhook URL for a specific bot. This includes all event types, their payload structures, and any additional metadata. Useful for developers to understand and integrate webhook functionality into their applications.

**Parameters:**
- None

**Returns:**
- `success`: Boolean (true if documentation retrieved successfully, false in case of any errors)
- `data`: [Bot webhook documentation data](/docs/api/reference/webhooks/bot_webhook_documentation#bot-webhook-event-types)
- `error`: Error, if any

```typescript
const { success, data, error } = await client.getBotWebhookDocumentation();

if (success) {
  console.log("Bot Webhook documentation:", data);
} else {
  console.error("Error getting bot webhook documentation:", error);
}
```

### `getCalendarWebhookDocumentation`

Retrieves the full documentation for the webhook events that Meeting BaaS sends to your webhook URL for a specific calendar. This includes all event types, their payload structures, and any additional metadata. Useful for developers to understand and integrate webhook functionality into their applications.

**Parameters:**
- None

**Returns:**
- `success`: Boolean (true if documentation retrieved successfully, false in case of any errors)
- `data`: [Calendar webhook documentation data](/docs/api/reference/webhooks/calendar_webhook_documentation#calendar-webhook-event-types)
- `error`: Error, if any

```typescript
const { success, data, error } = await client.getCalendarWebhookDocumentation();

if (success) {
  console.log("Calendar Webhook documentation:", data);
} else {
  console.error("Error getting calendar webhook documentation:", error);
}
```

## Response Types

All SDK methods return a discriminated union response:

```typescript
type ApiResponse<T> = 
  | { success: true; data: T; error?: never }
  | { success: false; error: ZodError | Error; data?: never }
```

### Success Response
When `success` is `true`, the response contains:
- `data`: The actual response data of type `T`
- `error`: Never present

### Error Response
When `success` is `false`, the response contains:
- `error`: Either a `ZodError` (validation error) or `Error` (API error)
- `data`: Never present

## Error Handling

The SDK provides type-safe error handling:

```typescript
import { ZodError } from "zod";

const result = await client.joinMeeting({
  meeting_url: "https://meet.google.com/abc-def-ghi",
  bot_name: "My Bot"
});

if (result.success) {
  // TypeScript knows result.data is JoinResponse
  console.log("Bot ID:", result.data.bot_id);
} else {
  // TypeScript knows result.error is ZodError | Error
  if (result.error instanceof ZodError) {
    console.error("Validation error:", result.error.errors);
  } else {
    console.error("API error:", result.error.message);
  }
}
```

## TypeScript Support

The SDK provides full TypeScript support with generated types from the OpenAPI specification:

```typescript
import type { 
  JoinRequest, 
  JoinResponse, 
  CreateCalendarParams,
  BotParam2,
  Metadata 
} from "@meeting-baas/sdk";

// All types are available for advanced usage
const joinParams: JoinRequest = {
  meeting_url: "https://meet.google.com/abc-def-ghi",
  bot_name: "My Bot",
  reserved: false
};
```

## Related Documentation

- **[Getting Started](/docs/typescript-sdk/getting-started)** - Quick setup guide
- **[Quick Start](/docs/typescript-sdk/quick-start)** - Comprehensive usage examples
- **[Integration Guide](/docs/typescript-sdk/integration)** - Advanced integration patterns
- **[MCP Tools](/docs/typescript-sdk/mpc-tools)** - Using with Model Context Protocol
- **[Advanced Examples](/docs/typescript-sdk/advanced-examples)** - Complex use cases


---

## Getting Started

Learn how to install and use the Meeting BaaS TypeScript SDK.

### Source: ./content/docs/typescript-sdk/getting-started.mdx

<Callout type="info">
  This guide uses v2 API, the recommended version for new projects. Get your API key at [dashboard.meetingbaas.com](https://dashboard.meetingbaas.com/onboarding).
</Callout>

<Steps>
<Step>
### Install the Package

Install the Meeting BaaS SDK using your preferred package manager:

<Tabs groupId='package-manager' persist items={['npm', 'pnpm', 'yarn']}>

```bash tab="npm"
npm install @meeting-baas/sdk
```

```bash tab="pnpm"
pnpm add @meeting-baas/sdk
```

```bash tab="yarn"
yarn add @meeting-baas/sdk
```

</Tabs>

</Step>

<Step>
### Create a Client

Create a new instance of the BaaS client with your API key:

```typescript
import { createBaasClient } from "@meeting-baas/sdk";

// Create a v2 BaaS client (recommended for new projects)
const client = createBaasClient({
  api_key: "your-api-key", // Get yours at https://dashboard.meetingbaas.com
  api_version: "v2"
});
```

<Callout type="warn">
  Make sure to keep your API key secure and never expose it in client-side code.
</Callout>

</Step>

<Step>
### Invoke Meeting BaaS methods

With this client instance created, you can call Meeting BaaS methods, such as:

```typescript
// Create a bot to join a meeting
const { success, data, error } = await client.createBot({
  bot_name: "Meeting Assistant",
  meeting_url: "https://meet.google.com/abc-def-ghi",
});

if (success) {
  console.log("Bot created successfully:", data.bot_id);
} else {
  console.error("Error creating bot:", error);
}
```

```typescript
// Have a bot leave the meeting
const { success, data, error } = await client.leaveBot({
  bot_id: "123e4567-e89b-12d3-a456-426614174000"
});

if (success) {
  console.log("Bot left the meeting successfully");
} else {
  console.error("Error leaving meeting:", error);
}
```

</Step>
</Steps> 


---

## Introduction

Get started with the Meeting BaaS TypeScript SDK

### Source: ./content/docs/typescript-sdk/index.mdx


<Callout type="info">
  We provide optimized documentation for both LLMs and recent MCP server updates. For more on our LLM integration, 
  see [LLMs](../llms/sdk) and for MCP access, visit [auth.meetingbaas.com](https://auth.meetingbaas.com/home).
</Callout>

<Callout type="info">
  **New to Meeting BaaS?** Start with v2 API - it's our recommended version with enhanced security, better error handling, and more features.
  Sign up at [dashboard.meetingbaas.com](https://dashboard.meetingbaas.com/onboarding).
</Callout>

<Callout type="warn">
  **Migrating from v1?** SDK v6.0.0 supports both v1 and v2 APIs. See our [Migration to v2 API Guide](/docs/typescript-sdk/migration-to-v2) for upgrade instructions.
</Callout>

## Introduction

The **Meeting BaaS SDK** is the officially supported TypeScript package that empowers developers to integrate with the Meeting BaaS API - the universal interface for automating meetings across Google Meet, Zoom, and Microsoft Teams. This SDK provides:

- **Complete type safety** with comprehensive TypeScript definitions and discriminated union responses
- **Automatic parameter validation** using Zod schemas for all API calls
- **Simplified error handling** with no try/catch required for API errors
- **Tree-shakeable client** for optimized bundle sizes
- **Cross-platform consistency** for all supported meeting providers

## Quick Example

```typescript
import { createBaasClient } from "@meeting-baas/sdk";

// Create a v2 client (recommended for new projects)
const client = createBaasClient({
  api_key: "your-api-key", // Get yours at https://dashboard.meetingbaas.com
  api_version: "v2"
});

// Create a bot to join and record a meeting
const { success, data, error } = await client.createBot({
  bot_name: "Meeting Assistant",
  meeting_url: "https://meet.google.com/abc-def-ghi",
});

if (success) {
  console.log("Bot created successfully:", data.bot_id);
} else {
  console.error("Error creating bot:", error);
}
```

<Cards>
  <Card title="GitHub Repository" icon={<Github className="text-gray-400" />} href="https://github.com/Meeting-Baas/sdk-generator">
    View the source code, contribute, and track issues on GitHub.
  </Card>
  <Card title="npm Package" icon={<Package className="text-red-400" />} href="https://www.npmjs.com/package/@meeting-baas/sdk">
    Install and manage the SDK through npm.
  </Card>
</Cards>

## Features

<Cards>
  <Card title="Type-Safe API Client" icon={<Code className="text-blue-400" />}>
    Factory-based client creation with discriminated union responses for type-safe error handling.
    All parameters automatically validated using Zod schemas.
  </Card>
  <Card title="Bot Management" icon={<Bot className="text-green-400" />}>
    Create, join, and manage meeting bots across platforms including Google
    Meet, Zoom, and Microsoft Teams with comprehensive lifecycle management.
  </Card>
  <Card
    title="Calendar Integration"
    icon={<Calendar className="text-purple-400" />}
  >
    Connect calendars and automatically schedule meeting recordings with support
    for Google Calendar and Microsoft Outlook integration.
  </Card>
  <Card
    title="Complete API Coverage"
    icon={<CheckCircle className="text-teal-400" />}
  >
    Access to all Meeting BaaS API endpoints with consistent, well-documented
    interfaces and automatic code generation from OpenAPI specification.
  </Card>
  <Card
    title="Enhanced TypeScript Support"
    icon={<FileType className="text-orange-400" />}
  >
    Full TypeScript definitions for all APIs, including request/response types,
    discriminated union responses, and comprehensive error handling.
  </Card>
  <Card
    title="Flexible MCP Integration"
    icon={<Wrench className="text-yellow-400" />}
  >
    Use SDK functions directly in your MCP tool handlers for complete control over
    schemas, descriptions, and error handling in your Model Context Protocol servers.
  </Card>
  <Card title="Node.js Compatibility" icon={<ShieldCheck className="text-red-400" />}>
    Compatible and tested with Node.js versions 18, 19, 20, 21, and 22.
    Comprehensive test coverage across all supported versions.
  </Card>
  <Card
    title="Automated Updates"
    icon={<Cog className="text-indigo-400" />}
  >
    SDK automatically stays up-to-date with API changes through daily automated
    workflows. No manual intervention required.
  </Card>
  <Card
    title="Tree-Shakeable Bundle"
    icon={<Package className="text-pink-400" />}
  >
    Optimized bundle with tree shaking capabilities. Only import and ship the
    methods you actually use in your application.
  </Card>
</Cards>

## Learn More

<Cards>
  <Card
    title="Getting Started"
    icon={<Download />}
    href="/docs/typescript-sdk/getting-started"
  >
    Quick start guide with installation and basic usage examples.
  </Card>
  <Card
    title="Quick Start"
    icon={<Zap />}
    href="/docs/typescript-sdk/quick-start"
  >
    Comprehensive guide with advanced examples and integration patterns.
  </Card>
  <Card
    title="Integration"
    icon={<FileText />}
    href="/docs/typescript-sdk/integration"
  >
    Learn how to integrate the Meeting BaaS SDK with your applications and MCP servers.
  </Card>
  <Card
    title="MCP Tools"
    icon={<Wrench />}
    href="/docs/typescript-sdk/mpc-tools"
  >
    Using the SDK with Model Context Protocol servers.
  </Card>
  <Card
    title="Advanced Examples"
    icon={<MessageSquareText />}
    href="/docs/typescript-sdk/advanced-examples"
  >
    Complex integration patterns and use cases.
  </Card>
  <Card
    title="API Reference"
    icon={<SquareFunction />}
    href="/docs/typescript-sdk/complete-reference"
  >
    Complete API documentation for all SDK methods and types.
  </Card>
  <Card
    title="Migration to v2 API"
    icon={<ArrowUpCircle />}
    href="/docs/typescript-sdk/migration-to-v2"
  >
    Guide for migrating to v6.0.0 with v2 API support.
  </Card>
</Cards>


---

## Integration

Learn how to integrate the Meeting BaaS SDK with your applications and MCP servers.

### Source: ./content/docs/typescript-sdk/integration.mdx


<Callout type="info">
  This guide uses v2 API, the recommended version for new projects. Get your API key at [dashboard.meetingbaas.com](https://dashboard.meetingbaas.com/onboarding).
</Callout>

## SDK Integration

The Meeting BaaS SDK provides a clean, type-safe interface for integrating with the Meeting BaaS API. Here are the main integration patterns:

### Basic SDK Integration

The simplest way to integrate the SDK:

```typescript
import { createBaasClient } from '@meeting-baas/sdk';

// Create a v2 BaaS client with your API key
const client = createBaasClient({
  api_key: process.env.MEETING_BAAS_API_KEY,
  api_version: 'v2'
});

// Use the client for API calls
const { success, data, error } = await client.createBot({
  bot_name: 'My Bot',
  meeting_url: 'https://meet.google.com/abc-def-ghi',
});

if (success) {
  console.log('Bot created successfully:', data.bot_id);
} else {
  console.error('Error creating bot:', error);
}
```

### MCP Server Integration

For MCP (Model Context Protocol) server integration, you can use the SDK functions directly within your tool handlers:

```typescript
import { createBaasClient } from "@meeting-baas/sdk"
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { z } from "zod"

// Create an MCP server
const server = new McpServer({
  name: "demo-server",
  version: "1.0.0"
})

// @modelcontextprotocol/sdk expects the input schema to be a ZodRawShape (plain object with zod types)
const createBotInputSchema = {
  bot_name: z.string().default("Meeting BaaS Bot"),
  meeting_url: z.string()
}

// Add a createBot tool
server.registerTool(
  "createBot",
  {
    title: "Send a Meeting BaaS bot to a meeting",
    description:
      "Send a Meeting BaaS bot to a Google Meet/Teams/Zoom meeting to automatically record and transcribe the meeting with speech diarization",
    inputSchema: createBotInputSchema
  },
  async (args) => {
    const client = createBaasClient({
      api_key: "your-api-key",
      api_version: "v2"
    })

    const { success, data, error } = await client.createBot(args)

    if (success) {
      return {
        content: [{ type: "text", text: `Successfully created bot: ${JSON.stringify(data)}` }]
      }
    }

    return {
      content: [{ type: "text", text: `Failed to create bot: ${error}` }]
    }
  }
)
```

### Calendar Integration

For calendar integration:

```typescript
import { createBaasClient } from '@meeting-baas/sdk';

const client = createBaasClient({
  api_key: 'your-api-key',
  api_version: 'v2'
});

// Create a calendar connection
const calendarResult = await client.createCalendarConnection({
  calendar_platform: 'google',
  oauth_client_id: 'your-oauth-client-id',
  oauth_client_secret: 'your-oauth-client-secret',
  oauth_refresh_token: 'your-oauth-refresh-token',
  raw_calendar_id: 'primary'
});

if (calendarResult.success) {
  console.log('Calendar connected:', calendarResult.data);

  // List all calendars
  const calendarsResult = await client.listCalendars();
  if (calendarsResult.success) {
    console.log('All calendars:', calendarsResult.data);
  }

  // List events from a calendar
  const eventsResult = await client.listEvents({
    calendar_id: calendarResult.data.calendar_id
  });
  
  if (eventsResult.success) {
    console.log('Events:', eventsResult.data);
  }

  // Schedule a bot for an event
  if (eventsResult.success && eventsResult.data.events.length > 0) {
    const scheduleResult = await client.createCalendarBot({
      calendar_id: calendarResult.data.calendar_id,
      body: {
        event_id: eventsResult.data.events[0].event_id,
        bot_name: 'Event Recording Bot'
      }
    });
    
    if (scheduleResult.success) {
      console.log('Bot scheduled for event successfully');
    }
  }
} else {
  console.error('Error connecting calendar:', calendarResult.error);
}
```

### Next.js API Route Example

For Next.js applications:

```typescript
// app/api/meeting-baas/route.ts
import { createBaasClient } from "@meeting-baas/sdk";

export async function POST(req: Request) {
  const { meeting_url, bot_name } = await req.json();

  const client = createBaasClient({
    api_key: process.env.MEETING_BAAS_API_KEY!,
    api_version: 'v2'
  });

  const result = await client.createBot({
    meeting_url,
    bot_name: bot_name || 'Meeting BaaS Bot',
  });

  if (result.success) {
    return Response.json({ 
      success: true, 
      bot_id: result.data.bot_id 
    });
  } else {
    return Response.json({ 
      success: false, 
      error: result.error,
      code: result.code
    }, { status: result.statusCode || 400 });
  }
}
```

### Express.js Integration

For Express.js applications:

```typescript
import express from 'express';
import { createBaasClient } from '@meeting-baas/sdk';

const app = express();
app.use(express.json());

const client = createBaasClient({
  api_key: process.env.MEETING_BAAS_API_KEY!,
  api_version: 'v2'
});

app.post('/create-bot', async (req, res) => {
  const { meeting_url, bot_name } = req.body;

  const result = await client.createBot({
    meeting_url,
    bot_name: bot_name || 'Meeting BaaS Bot',
  });

  if (result.success) {
    res.json({ 
      success: true, 
      bot_id: result.data.bot_id 
    });
  } else {
    res.status(result.statusCode || 400).json({ 
      success: false, 
      error: result.error,
      code: result.code
    });
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});
```


---

## MCP Tools

Learn how to create MCP (Model Context Protocol) tools using the Meeting BaaS SDK functions.

### Source: ./content/docs/typescript-sdk/mcp-tools.mdx


## Creating MCP Tools with the SDK

The Meeting BaaS SDK v5.0.0 provides a clean approach for creating MCP tools by using the SDK functions directly within your tool handlers. This gives you full control over tool schemas, descriptions, and registration.

### Basic MCP Tool Creation

Here's how to create MCP tools using the SDK:

```typescript
import { type JoinRequest, createBaasClient } from "@meeting-baas/sdk"
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { z } from "zod"

// Create an MCP server
const server = new McpServer({
  name: "meeting-baas-server",
  version: "1.0.0"
})

// Define the input schema for the join meeting tool
const joinToolInputSchema = {
  bot_name: z.string().default("Meeting BaaS Bot"),
  meeting_url: z.string(),
  reserved: z.boolean().default(false)
}

// Register the join meeting tool
server.registerTool(
  "joinMeeting",
  {
    title: "Send a Meeting BaaS bot to a meeting",
    description:
      "Send a Meeting BaaS bot to a Google Meet/Teams/Zoom meeting to automatically record and transcribe the meeting with speech diarization",
    inputSchema: joinToolInputSchema
  },
  async (args) => {
    const client = createBaasClient({
      api_key: "your-api-key"
    })

    const { success, data, error } = await client.joinMeeting(args as JoinRequest)

    if (success) {
      return {
        content: [{ type: "text", text: `Successfully joined meeting: ${JSON.stringify(data)}` }]
      }
    }

    return {
      content: [{ type: "text", text: `Failed to join meeting: ${error}` }]
    }
  }
)
```

### Multiple Tools Example

Here's how to create multiple MCP tools:

```typescript
import { 
  type JoinRequest, 
  type GetMeetingDataParams,
  type LeaveRequest,
  createBaasClient 
} from "@meeting-baas/sdk"
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { z } from "zod"

const server = new McpServer({
  name: "meeting-baas-server",
  version: "1.0.0"
})

// Join Meeting Tool
server.registerTool(
  "joinMeeting",
  {
    title: "Send a Meeting BaaS bot to a meeting",
    description: "Send a Meeting BaaS bot to a meeting to record and transcribe",
    inputSchema: {
      bot_name: z.string().default("Meeting BaaS Bot"),
      meeting_url: z.string(),
      reserved: z.boolean().default(false)
    }
  },
  async (args) => {
    const client = createBaasClient({ api_key: "your-api-key" })
    const { success, data, error } = await client.joinMeeting(args as JoinRequest)
    
    if (success) {
      return {
        content: [{ type: "text", text: `Bot joined successfully with ID: ${data.bot_id}` }]
      }
    }
    
    return {
      content: [{ type: "text", text: `Failed to join meeting: ${error}` }]
    }
  }
)

// Get Meeting Data Tool
server.registerTool(
  "getMeetingData",
  {
    title: "Get meeting recording and metadata",
    description: "Retrieve meeting data including transcripts and recording URL",
    inputSchema: {
      bot_id: z.string(),
      include_transcripts: z.boolean().default(true)
    }
  },
  async (args) => {
    const client = createBaasClient({ api_key: "your-api-key" })
    const { success, data, error } = await client.getMeetingData(args as GetMeetingDataParams)
    
    if (success) {
      return {
        content: [{ 
          type: "text", 
          text: `Meeting data retrieved: Duration: ${data.duration}s, MP4: ${data.mp4 || 'Not available'}` 
        }]
      }
    }
    
    return {
      content: [{ type: "text", text: `Failed to get meeting data: ${error}` }]
    }
  }
)

// Leave Meeting Tool
server.registerTool(
  "leaveMeeting",
  {
    title: "Have a bot leave a meeting",
    description: "Make a bot leave the meeting it's currently in",
    inputSchema: {
      uuid: z.string()
    }
  },
  async (args) => {
    const client = createBaasClient({ api_key: "your-api-key" })
    const { success, data, error } = await client.leaveMeeting(args as LeaveRequest)
    
    if (success) {
      return {
        content: [{ type: "text", text: `Bot left meeting successfully: ${data.bot_id}` }]
      }
    }
    
    return {
      content: [{ type: "text", text: `Failed to leave meeting: ${error}` }]
    }
  }
)
```

### Calendar Tools Example

Here's how to create calendar-related MCP tools:

```typescript
import { 
  type CreateCalendarParams,
  type ListCalendarEventsParams,
  createBaasClient 
} from "@meeting-baas/sdk"
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { z } from "zod"

const server = new McpServer({
  name: "meeting-baas-calendar-server",
  version: "1.0.0"
})

// Create Calendar Tool
server.registerTool(
  "createCalendar",
  {
    title: "Create a calendar integration",
    description: "Integrate a new calendar with Meeting BaaS using OAuth credentials",
    inputSchema: {
      oauth_client_id: z.string(),
      oauth_client_secret: z.string(),
      oauth_refresh_token: z.string(),
      platform: z.enum(["Google", "Microsoft"]),
      raw_calendar_id: z.string().optional()
    }
  },
  async (args) => {
    const client = createBaasClient({ api_key: "your-api-key" })
    const { success, data, error } = await client.createCalendar(args as CreateCalendarParams)
    
    if (success) {
      return {
        content: [{ 
          type: "text", 
          text: `Calendar created successfully: ${data.calendar.name} (${data.calendar.uuid})` 
        }]
      }
    }
    
    return {
      content: [{ type: "text", text: `Failed to create calendar: ${error}` }]
    }
  }
)

// List Calendar Events Tool
server.registerTool(
  "listCalendarEvents",
  {
    title: "List calendar events",
    description: "Get a list of events from a specific calendar",
    inputSchema: {
      calendar_id: z.string(),
      start_date_gte: z.string().optional(),
      start_date_lte: z.string().optional(),
      status: z.enum(["upcoming", "past", "all"]).default("upcoming")
    }
  },
  async (args) => {
    const client = createBaasClient({ api_key: "your-api-key" })
    const { success, data, error } = await client.listCalendarEvents(args as ListCalendarEventsParams)
    
    if (success) {
      const eventCount = data.events.length
      return {
        content: [{ 
          type: "text", 
          text: `Found ${eventCount} events in calendar. Next cursor: ${data.next || 'None'}` 
        }]
      }
    }
    
    return {
      content: [{ type: "text", text: `Failed to list events: ${error}` }]
    }
  }
)
```

### Error Handling Best Practices

When creating MCP tools, it's important to handle errors gracefully:

```typescript
server.registerTool(
  "joinMeeting",
  {
    title: "Join Meeting",
    description: "Join a meeting with a bot",
    inputSchema: {
      meeting_url: z.string(),
      bot_name: z.string().default("Meeting BaaS Bot")
    }
  },
  async (args) => {
    try {
      const client = createBaasClient({ api_key: "your-api-key" })
      const { success, data, error } = await client.joinMeeting(args)
      
      if (success) {
        return {
          content: [{ 
            type: "text", 
            text: `Successfully joined meeting. Bot ID: ${data.bot_id}` 
          }]
        }
      } else {
        // Handle API errors
        if (error instanceof z.ZodError) {
          return {
            content: [{ 
              type: "text", 
              text: `Validation error: ${error.errors.map(e => e.message).join(', ')}` 
            }]
          }
        }
        
        return {
          content: [{ 
            type: "text", 
            text: `API error: ${error.message}` 
          }]
        }
      }
    } catch (unexpectedError) {
      // Handle unexpected errors
      return {
        content: [{ 
          type: "text", 
          text: `Unexpected error: ${unexpectedError}` 
        }]
      }
    }
  }
)
```

### Available SDK Methods for MCP Tools

You can create MCP tools for any of these SDK methods:

#### Bot Management
- `joinMeeting` - Have a bot join a meeting
- `leaveMeeting` - Have a bot leave a meeting
- `getMeetingData` - Get meeting recording and metadata
- `deleteBotData` - Delete bot data
- `listBots` - List bots with metadata
- `retranscribeBot` - Retranscribe bot recordings
- `getScreenshots` - Get bot screenshots

#### Calendar Management
- `createCalendar` - Create a calendar integration
- `listCalendars` - List all calendars
- `getCalendar` - Get calendar details
- `updateCalendar` - Update calendar credentials
- `deleteCalendar` - Delete a calendar
- `getCalendarEvent` - Get event details
- `scheduleCalendarRecordEvent` - Schedule recording for an event
- `unscheduleCalendarRecordEvent` - Unschedule recording
- `patchBot` - Update scheduled bot configuration
- `listCalendarEvents` - List calendar events
- `resyncAllCalendars` - Resync all calendars
- `listRawCalendars` - List raw calendars from provider

#### Webhooks
- `getWebhookDocumentation` - Get webhook documentation
- `getBotWebhookDocumentation` - Get bot webhook documentation
- `getCalendarWebhookDocumentation` - Get calendar webhook documentation

### Benefits of This Approach

1. **Full Control**: You have complete control over tool schemas and descriptions
2. **Type Safety**: Full TypeScript support with generated types
3. **Flexibility**: Customize error handling and responses
4. **Consistency**: Use the same SDK functions across your application
5. **Maintainability**: Easy to update when the SDK changes


---

## Migration to v2 API

Guide for migrating from Meeting BaaS SDK v5.x to v6.0.0 with v2 API support.

### Source: ./content/docs/typescript-sdk/migration-to-v2.mdx


<Callout type="info">
  **SDK v6.0.0** adds support for Meeting BaaS v2 API while maintaining full backward compatibility with v1 API. All existing v1 code continues to work without changes.
</Callout>

## About Meeting BaaS v2

Meeting BaaS v2 is a new revamp of API platform, built on your feedback and the experience of building a meeting bot api of the last two years.

<Cards>
  <Card title="v2 Release Notes" icon={<FileText />} href="https://www.meetingbaas.com/en/api/introducing-meeting-baas-v2">
    Read the full announcement with all new features, improvements, and what's coming next.
  </Card>
  <Card title="v2 API Migration Guide" icon={<ArrowRight />} href="/docs/api-v2/migration-guide">
    Complete guide to migrating your API calls from v1 to v2 (without the SDK).
  </Card>
</Cards>

## What's New in SDK v6.0.0

- **Dual API Support**: Support for both Meeting BaaS v1 and v2 APIs in parallel
- **Type-Safe Version Selection**: TypeScript automatically infers available methods based on `api_version`
- **Pass-Through v2 Responses**: v2 API responses are passed through without transformation
- **Backward Compatible**: All existing v1 code continues to work without changes
- **Easy Migration Path**: Simply change `api_version: "v2"` to migrate to v2 API

## No Breaking Changes

v6.0.0 is **fully backward compatible** with v5.x. All existing code using v1 API will continue to work without any changes.

## Configuration Options

The client accepts the following configuration options:

| Option | Type | Required | Default | Description |
|--------|------|----------|---------|-------------|
| `api_key` | `string` | ✅ Yes | - | Your Meeting BaaS API key. Get yours at [meetingbaas.com](https://meetingbaas.com) |
| `api_version` | `"v1" \| "v2"` | ❌ No | `"v1"` | API version to use. Use `"v2"` for the new Meeting BaaS v2 API |
| `timeout` | `number` | ❌ No | `30000` | Request timeout in milliseconds |

```typescript
interface BaasClientConfig {
  api_key: string;           // Required: Your Meeting BaaS API key
  api_version?: "v1" | "v2"; // Optional: API version (default: "v1")
  base_url?: string;         // Optional: Base URL (internal use)
  timeout?: number;          // Optional: Request timeout in ms (default: 30000)
}
```

## Update Dependencies

```bash tab="npm"
npm install @meeting-baas/sdk@^6.0.0
```

```bash tab="pnpm"
pnpm add @meeting-baas/sdk@^6.0.0
```

```bash tab="yarn"
yarn add @meeting-baas/sdk@^6.0.0
```

## Using the v2 API

### Basic Usage

To use the v2 API, specify `api_version: "v2"` when creating the client:

```typescript
import { createBaasClient } from "@meeting-baas/sdk";

// v1 API (default, backward compatible)
const v1Client = createBaasClient({
  api_key: "your-api-key"
  // api_version defaults to "v1"
});

// v2 API
const v2Client = createBaasClient({
  api_key: "your-api-key",
  api_version: "v2"
});
```

### Type-Safe Method Access

TypeScript automatically infers which methods are available based on the API version:

```typescript
// v1 client - only v1 methods available
const v1Client = createBaasClient({ api_key: "key" });
v1Client.joinMeeting({ ... }); // ✅ Available
v1Client.createBot({ ... }); // ❌ TypeScript error - not available

// v2 client - only v2 methods available
const v2Client = createBaasClient({ api_key: "key", api_version: "v2" });
v2Client.createBot({ ... }); // ✅ Available
v2Client.joinMeeting({ ... }); // ❌ TypeScript error - not available
```

## Response Format Differences

### v1 API Response

```typescript
type ApiResponse<T> =
  | { success: true; data: T; error?: never }
  | { success: false; error: ZodError | Error; data?: never }
```

### v2 API Response

```typescript
type ApiResponseV2<T> =
  | { success: true; data: T }
  | { success: false; error: string; code: string; statusCode: number; details: unknown | null }
```

**Key Differences:**

- v1: SDK wraps responses, `error` can be `ZodError | Error`
- v2: API already returns structured format, SDK passes through as-is
- v2 error responses include `code`, `statusCode`, and `details` fields

### Batch Routes (v2)

v2 batch routes return a special format for partial success:

```typescript
// Batch response format
{
  success: true,
  data: [...], // Successful items
  errors: [...] // Failed items with error details
}

// Example: batchCreateBots
const result = await v2Client.batchCreateBots({
  bots: [...]
});

if (result.success) {
  console.log("Successful:", result.data);
  if (result.errors.length > 0) {
    console.log("Failed:", result.errors);
  }
}
```

## v1 to v2 Method Mapping

When migrating from v1 to v2, use this mapping to find the equivalent v2 methods:

### Bot Methods

| v1 Method | v2 Method | Notes |
|-----------|-----------|-------|
| `joinMeeting` | `createBot` | Creates and joins immediately |
| `leaveMeeting` | `leaveBot` | - |
| `getMeetingData` | `getBotDetails` | - |
| `deleteBotData` | `deleteBotData` | Now accepts `delete_from_provider` option |
| `listBots` | `listBots` | - |
| `retranscribeBot` | - | Not available in v2 |
| `getScreenshots` | `getBotScreenshots` | Now supports pagination |

### Calendar Methods

| v1 Method | v2 Method | Notes |
|-----------|-----------|-------|
| `createCalendar` | `createCalendarConnection` | - |
| `listCalendars` | `listCalendars` | - |
| `getCalendar` | `getCalendarDetails` | - |
| `updateCalendar` | `updateCalendarConnection` | - |
| `deleteCalendar` | `deleteCalendarConnection` | - |
| `listCalendarEvents` | `listEvents` | Now requires `calendar_id` in path |
| `getCalendarEvent` | `getEventDetails` | Now requires `calendar_id` in path |
| `scheduleCalendarRecordEvent` | `createCalendarBot` | - |
| `unscheduleCalendarRecordEvent` | `deleteCalendarBot` | - |
| `patchBot` | `updateCalendarBot` | - |
| `listRawCalendars` | `listRawCalendars` | - |
| `resyncAllCalendars` | `syncCalendar` | Now per-calendar |

### New v2-Only Methods

These methods are only available in v2:

- `batchCreateBots` - Create multiple bots at once
- `getBotStatus` - Get bot status separately from details
- `resendFinalWebhook` - Resend the final webhook for a bot
- `retryCallback` - Retry a failed callback
- `createScheduledBot` - Create a bot scheduled for a future time
- `batchCreateScheduledBots` - Create multiple scheduled bots
- `listScheduledBots` - List scheduled bots
- `getScheduledBot` - Get scheduled bot details
- `updateScheduledBot` - Update a scheduled bot
- `deleteScheduledBot` - Delete a scheduled bot
- `listEventSeries` - List recurring event series
- `resubscribeCalendar` - Resubscribe to calendar webhooks

## Migration Examples

### Example 1: Migrating Bot Creation from v1 to v2

**Before (v1 API):**

```typescript
import { createBaasClient } from "@meeting-baas/sdk";

const client = createBaasClient({
  api_key: "your-api-key"
});

const result = await client.joinMeeting({
  meeting_url: "https://meet.google.com/abc-def-ghi",
  bot_name: "My Bot",
  reserved: true
});

if (result.success) {
  console.log("Bot ID:", result.data.bot_id);
}
```

**After (v2 API):**

```typescript
import { createBaasClient } from "@meeting-baas/sdk";

const client = createBaasClient({
  api_key: "your-api-key",
  api_version: "v2" // Only change needed!
});

// v2 uses createBot instead of joinMeeting
const result = await client.createBot({
  meeting_url: "https://meet.google.com/abc-def-ghi",
  bot_name: "My Bot"
});

if (result.success) {
  console.log("Bot ID:", result.data.bot_id);
} else {
  // v2 error format includes code and statusCode
  console.error("Error:", result.error);
  console.error("Code:", result.code);
  console.error("Status:", result.statusCode);
}
```

### Example 2: Using Both APIs in Parallel

You can use both APIs in the same codebase:

```typescript
import { createBaasClient } from "@meeting-baas/sdk";

const v1Client = createBaasClient({
  api_key: "your-api-key",
  api_version: "v1"
});

const v2Client = createBaasClient({
  api_key: "your-api-key",
  api_version: "v2"
});

// Use v1 for legacy operations
const v1Result = await v1Client.joinMeeting({ ... });

// Use v2 for new features
const v2Result = await v2Client.createBot({ ... });
```

## v2 API Methods Reference

### Bot Management

| Method | Description |
|--------|-------------|
| `createBot` | Create a bot |
| `batchCreateBots` | Create multiple bots |
| `listBots` | List bots |
| `getBotDetails` | Get bot details |
| `getBotStatus` | Get bot status |
| `getBotScreenshots` | Get bot screenshots |
| `leaveBot` | Leave meeting |
| `deleteBotData` | Delete bot data |
| `resendFinalWebhook` | Resend final webhook |
| `retryCallback` | Retry callback |

### Scheduled Bot Management

| Method | Description |
|--------|-------------|
| `createScheduledBot` | Create scheduled bot |
| `batchCreateScheduledBots` | Create multiple scheduled bots |
| `listScheduledBots` | List scheduled bots |
| `getScheduledBot` | Get scheduled bot details |
| `updateScheduledBot` | Update scheduled bot |
| `deleteScheduledBot` | Delete scheduled bot |

### Calendar Connection Management

| Method | Description |
|--------|-------------|
| `listRawCalendars` | List raw calendars (preview before creating connection) |
| `createCalendarConnection` | Create calendar connection |
| `listCalendars` | List calendar connections |
| `getCalendarDetails` | Get calendar connection details |
| `updateCalendarConnection` | Update calendar connection |
| `deleteCalendarConnection` | Delete calendar connection |
| `syncCalendar` | Sync calendar events |
| `resubscribeCalendar` | Resubscribe to calendar webhooks |

### Calendar Event Management

| Method | Description |
|--------|-------------|
| `listEvents` | List calendar events |
| `listEventSeries` | List event series |
| `getEventDetails` | Get event details |

### Calendar Bot Management

| Method | Description |
|--------|-------------|
| `createCalendarBot` | Schedule bot for calendar event |
| `updateCalendarBot` | Update calendar bot |
| `deleteCalendarBot` | Cancel calendar bot |

## Webhook Types (v2)

The v2 API includes comprehensive TypeScript types for all webhook events:

### Bot Webhooks

- `BotWebhookCompleted` - Sent when a bot successfully completes recording
- `BotWebhookFailed` - Sent when a bot fails to join or record
- `BotWebhookStatusChange` - Sent when a bot's status changes

### Calendar Webhooks

- `CalendarWebhookConnectionCreated` - Sent when a calendar connection is created
- `CalendarWebhookConnectionUpdated` - Sent when a calendar connection is updated
- `CalendarWebhookConnectionDeleted` - Sent when a calendar connection is deleted
- `CalendarWebhookEventCreated` - Sent when a calendar event is created
- `CalendarWebhookEventUpdated` - Sent when a calendar event is updated
- `CalendarWebhookEventCancelled` - Sent when a calendar event is cancelled
- `CalendarWebhookEventsSynced` - Sent when a calendar completes the initial sync

### Callback Payloads

- `CallbackCompleted` - Callback payload sent when a bot successfully completes recording (event: `"bot.completed"`)
- `CallbackFailed` - Callback payload sent when a bot fails to join or record (event: `"bot.failed"`)

<Callout type="info">
  Callback payloads have the same structure as webhook events but are sent to bot-specific callback URLs configured via `callback_config` when creating bots.
</Callout>

For detailed webhook documentation, see the [v2 API webhook reference](https://docs.meetingbaas.com/api-v2/reference/webhooks).

### Usage Example

```typescript
import type { V2 } from "@meeting-baas/sdk";

// Type-safe webhook handler
async function handleWebhook(payload: V2.BotWebhookCompleted) {
  if (payload.event === "bot.completed") {
    console.log("Bot completed:", payload.data.bot_id);
    console.log("Transcription:", payload.data.transcription);
    console.log("Video URL:", payload.data.video);
  }
}

// Handle multiple event types with discriminated unions
type WebhookEvent = 
  | V2.BotWebhookCompleted 
  | V2.BotWebhookFailed 
  | V2.BotWebhookStatusChange
  | V2.CalendarWebhookEventCreated;

async function handleAnyWebhook(payload: WebhookEvent) {
  switch (payload.event) {
    case "bot.completed":
      // TypeScript knows payload.data has BotWebhookCompletedData
      break;
    case "bot.failed":
      // TypeScript knows payload.data has BotWebhookFailedData
      break;
    // ... other cases
  }
}

// Callback payloads can be handled the same way
async function handleCallback(payload: V2.CallbackCompleted | V2.CallbackFailed) {
  if (payload.event === "bot.completed") {
    // TypeScript knows this is CallbackCompleted
    console.log("Callback - Bot completed:", payload.data.bot_id);
  } else if (payload.event === "bot.failed") {
    // TypeScript knows this is CallbackFailed
    console.log("Callback - Bot failed:", payload.data.bot_id);
  }
}
```

## Migration Checklist

<Steps>
<Step>
### Update Dependencies to SDK v6

```bash
npm install @meeting-baas/sdk@^6.0.0
```

</Step>

<Step>
### Test Existing Code

All existing v1 code should continue to work without changes. Test your application to ensure everything works as expected.

</Step>

<Step>
### Migrate to v2 (Optional)

If you want to use v2 API:

1. **Update client creation** to include `api_version: "v2"`
2. **Update method calls** to use v2 method names (e.g., `createBot` instead of `joinMeeting`)
3. **Update error handling** to use v2 error format (`code`, `statusCode`, `details`)
4. **Handle batch responses** if using batch operations (check `errors` array)

</Step>

<Step>
### Update Type Imports (if needed)

If you're importing types, they're now organized by version:

```typescript
// v1 types (from generated/v1/schema)
import type { JoinRequest, JoinResponse } from "@meeting-baas/sdk";

// v2 types (from generated/v2/schema)
import type { CreateBotRequest, CreateBotResponse } from "@meeting-baas/sdk";
```

</Step>
</Steps>

## Common Issues

### TypeScript Shows Wrong Methods

**Problem:** TypeScript shows v1 methods when you want v2, or vice versa.

**Solution:** Ensure `api_version` is correctly set in the client configuration:

```typescript
// Correct - must be explicitly set for v2
const client = createBaasClient({
  api_key: "key",
  api_version: "v2"
});
```

### Error Handling Differences

**Problem:** v2 error format is different from v1.

**Solution:** Update error handling to use v2 error fields:

```typescript
// v1 error handling
if (!result.success) {
  console.error(result.error); // ZodError | Error
}

// v2 error handling
if (!result.success) {
  console.error(result.error); // string
  console.error(result.code); // string
  console.error(result.statusCode); // number
  console.error(result.details); // unknown | null
}
```

### Batch Route Errors

**Problem:** Batch routes return `success: true` even when some items fail.

**Solution:** Check the `errors` array for partial failures:

```typescript
const result = await client.batchCreateBots({ bots: [...] });

if (result.success) {
  if (result.errors.length > 0) {
    // Some items failed
    console.log("Partial success:", result.data);
    console.log("Errors:", result.errors);
  } else {
    // All items succeeded
    console.log("All succeeded:", result.data);
  }
}
```

## Getting Help

If you encounter issues during migration:

1. Check this migration guide
2. Review the [API Reference](/docs/typescript-sdk/complete-reference) for current usage examples
3. Open an issue on [GitHub](https://github.com/Meeting-Baas/sdk-generator/issues)
4. Join our [Discord community](https://discord.com/invite/dsvFgDTr6c) for support



---

## Quick Start

Quick guide for integrating with Meeting BaaS services.

### Source: ./content/docs/typescript-sdk/quick-start.mdx


<Callout type="info">
  This guide uses v2 API, the recommended version for new projects. Get your API key at [dashboard.meetingbaas.com](https://dashboard.meetingbaas.com/onboarding).
</Callout>

```typescript
import { createBaasClient } from '@meeting-baas/sdk';

// Create a v2 BaaS client
const client = createBaasClient({
  api_key: 'your-api-key', // Get yours at https://dashboard.meetingbaas.com
  api_version: 'v2'
});

// Create a bot to join a meeting
const { success, data, error } = await client.createBot({
  bot_name: 'Meeting Assistant',
  meeting_url: 'https://meet.google.com/abc-def-ghi',
});

if (success) {
  console.log('Bot created successfully:', data.bot_id);
  
  // Get bot details
  const botDetails = await client.getBotDetails({
    bot_id: data.bot_id
  });
  
  if (botDetails.success) {
    console.log('Bot details:', botDetails.data);
  }
} else {
  console.error('Error creating bot:', error);
}
```

## Usage Examples

### Basic Usage

```typescript
import { createBaasClient } from '@meeting-baas/sdk';

// Create a v2 BaaS client
const client = createBaasClient({
  api_key: 'your-api-key',
  api_version: 'v2'
});

// Create a bot
const createResult = await client.createBot({
  bot_name: 'My Assistant',
  meeting_url: 'https://meet.google.com/abc-def-ghi',
});

if (createResult.success) {
  console.log('Bot created successfully:', createResult.data.bot_id);
  
  // Get bot details
  const botDetails = await client.getBotDetails({
    bot_id: createResult.data.bot_id
  });
  
  if (botDetails.success) {
    console.log('Bot details:', botDetails.data);
  } else {
    console.error('Error getting bot details:', botDetails.error);
  }
  
  // Delete bot data
  const deleteResult = await client.deleteBotData({
    bot_id: createResult.data.bot_id
  });
  
  if (deleteResult.success) {
    console.log('Bot data deleted successfully');
  }
} else {
  console.error('Error creating bot:', createResult.error);
}
```

### Calendar Integration

```typescript
import { createBaasClient } from '@meeting-baas/sdk';

const client = createBaasClient({
  api_key: 'your-api-key',
  api_version: 'v2'
});

// Create a calendar connection
const calendarResult = await client.createCalendarConnection({
  calendar_platform: 'google',
  oauth_client_id: 'your-oauth-client-id',
  oauth_client_secret: 'your-oauth-client-secret',
  oauth_refresh_token: 'your-oauth-refresh-token',
  raw_calendar_id: 'primary'
});

if (calendarResult.success) {
  console.log('Calendar connected:', calendarResult.data);

  // List all calendars
  const calendarsResult = await client.listCalendars();
  if (calendarsResult.success) {
    console.log('All calendars:', calendarsResult.data);
  }

  // List events from a calendar
  const eventsResult = await client.listEvents({
    calendar_id: calendarResult.data.calendar_id
  });
  
  if (eventsResult.success) {
    console.log('Events:', eventsResult.data);
  }
} else {
  console.error('Error connecting calendar:', calendarResult.error);
}
```

### Advanced Usage with Error Handling

```typescript
import { createBaasClient } from '@meeting-baas/sdk';

const client = createBaasClient({
  api_key: 'your-api-key',
  api_version: 'v2',
  timeout: 60000
});

async function comprehensiveExample() {
  // Create a bot with all options
  const createResult = await client.createBot({
    meeting_url: 'https://meet.google.com/abc-defg-hij',
    bot_name: 'Advanced Test Bot',
    bot_image: 'https://example.com/bot-image.jpg',
    entry_message: 'Hello from the advanced test bot!',
    recording_mode: 'speaker_view',
    transcription_config: { 
      provider: 'gladia' 
    },
    extra: { test_id: 'advanced-example' }
  });

  if (createResult.success) {
    const botId = createResult.data.bot_id;
    console.log('Bot created with ID:', botId);

    // Get bot status
    const statusResult = await client.getBotStatus({
      bot_id: botId
    });

    if (statusResult.success) {
      console.log('Bot status:', statusResult.data.status);
    }

    // Get bot details
    const detailsResult = await client.getBotDetails({
      bot_id: botId
    });

    if (detailsResult.success) {
      console.log('Bot details:', detailsResult.data);
    }

    // Leave the meeting
    const leaveResult = await client.leaveBot({
      bot_id: botId
    });

    if (leaveResult.success) {
      console.log('Bot left meeting successfully');
    }

    // Delete bot data
    const deleteResult = await client.deleteBotData({
      bot_id: botId
    });

    if (deleteResult.success) {
      console.log('Bot data deleted successfully');
    }
  } else {
    console.error('Error creating bot:', createResult.error);
    console.error('Error code:', createResult.code);
  }
}
```


---

## Authenticated Bots

Send bots that sign in as a real Google Workspace or Microsoft 365 user before joining, so they can enter sign-in-restricted meetings and bypass the waiting room

### Source: ./content/docs/api-v2/authenticated-bots/index.mdx


# Authenticated Bots

By default, Meeting BaaS bots join as **anonymous guests**. That's fine for open meetings, but it falls short when a meeting is **restricted to signed-in users**, restricted to the host's organization, or sends guests to a **waiting room / lobby**.

Authenticated bots solve this. Each bot signs in as a **real user** from an identity you control — a Google Workspace account for Google Meet, or a Microsoft 365 account for Microsoft Teams — before it joins the call. To the meeting, the bot looks like any other signed-in participant.

<Callout type="info">
Authentication is configured **per platform** and passed per bot through a platform-specific config object (`meet_config`, `teams_config`, `zoom_config`). Leave all of them `null` for anonymous joins.
</Callout>

## Choose your platform

<Cards>
  <Card title="Microsoft Teams Authentication" href="/docs/api-v2/authenticated-bots/teams">
    Sign in as a Microsoft 365 user with **stored credentials** (email + password). No SAML, no certificates — just an MFA-free account you provision.
  </Card>
  <Card title="Google Meet Authentication" href="/docs/api-v2/authenticated-bots/meet">
    Sign in as a Google Workspace user via **SAML SSO**. Meeting BaaS acts as the SAML IdP; you configure a Legacy SSO profile in the Google Admin Console.
  </Card>
  <Card title="Zoom Integration" href="/docs/api-v2/authenticated-bots/zoom">
    Authenticate Zoom bots with a stored Zoom credential and OBF/ZAK tokens via `zoom_config`.
  </Card>
</Cards>

## How the platforms compare

| | Microsoft Teams | Google Meet | Zoom |
|---|---|---|---|
| Config object | `teams_config` | `meet_config` | `zoom_config` |
| Sign-in mechanism | Username + password on `login.microsoftonline.com` | SAML SSO (Meeting BaaS is the IdP) | Stored Zoom credential + OBF/ZAK tokens |
| Parent resource | Teams Workspace (M365 domain, no keypair) | Meet Workspace (domain + SAML keypair) | Zoom credential |
| Per-user identity | Teams Login (`email` + `password`) | Meet Login (`email`) | — |
| One-time infra setup | Provision an **MFA-free** M365 account; no IdP/cert setup | Configure a Legacy SSO profile in Google Admin Console; upload the signing certificate | Store the Zoom credential |
| Round-robin pools | `email_group` | `email_group` | — |

## Shared concepts

Both Microsoft Teams and Google Meet authentication share the same operational model:

- **Workspaces group logins.** A workspace is the parent resource for one domain; logins are the individual user identities bots sign in as. Many logins can share one workspace.
- **Round-robin pools.** Group logins with an `email_group` and dispatch bots against that group — the least-loaded active login is assigned automatically. Capacity scales linearly with the number of active logins.
- **Fallback.** `fallback: "fail"` (default) fails bot creation when no slot is available; `fallback: "anonymous"` silently joins as a guest instead.
- **Health states.** Workspaces and logins carry a `state` (`active` / `invalid`). The system auto-disables a resource after a failure and records `last_error_message`; re-enable it with a `PATCH` after fixing the cause.
- **Secrets are write-only.** SAML private keys (Meet) and account passwords (Teams) are encrypted at rest with **AES-256-GCM** and are **never returned** in any API response.

## Related resources

- [Google Meet Authentication](/docs/api-v2/authenticated-bots/meet) — SAML SSO setup and usage
- [Microsoft Teams Authentication](/docs/api-v2/authenticated-bots/teams) — credential setup and usage
- [Alerts](/docs/api-v2/alerts) — monitor login-pool utilization and saturation
- [Error Codes](/docs/api-v2/error-codes) — `MEET_LOGIN_*` and `TEAMS_LOGIN_*` failure reasons


---

## Calendar integration

Learn how to integrate calendars and schedule bots automatically

### Source: ./content/docs/api-v2/getting-started/calendars.mdx


Meeting BaaS v2 allows you to connect calendars (Google Calendar, Microsoft Outlook) and automatically schedule bots for calendar events. This guide walks you through setting up calendar integration in your application.

## Overview

Calendar integration enables:

- Automatic bot scheduling for calendar events
- Real-time sync of calendar events via push subscriptions
- Webhook notifications for calendar changes
- Support for recurring events
- Automatic handling of event reschedules and cancellations

## Prerequisites

Before you can integrate calendars, you need to set up OAuth applications with Google and/or Microsoft. Meeting BaaS v2 uses a **bring-your-own-credentials** model, meaning you create and manage your own OAuth applications and provide the credentials when creating calendar connections.

### What You Need

You'll need two sets of credentials:

1. **Your Application's OAuth Credentials** (Service Level):
   - Google: OAuth 2.0 Client ID and Client Secret
   - Microsoft: Azure AD Application (Client) ID and Client Secret

2. **User's OAuth Refresh Token** (User Level):
   - OAuth refresh token obtained when each user authorizes your application to access their calendar

<Callout type="info">
  **Best Practice**: Request calendar access as a separate step after initial user signup. Users are more likely to grant calendar access when it's clearly tied to a specific feature they want to use.
</Callout>

## Create Google Calendar OAuth Application

You'll need to create a Google OAuth application that users can authorize to access their calendar. You can skip this step if your application won't support Google Calendar. We recommend creating separate applications for development and production.

### Steps

1. **Create a Google Cloud Project**: Follow the directions [here](https://support.google.com/cloud/answer/15549257) to create a new Google Cloud project that uses OAuth.

2. **Enable Google Calendar API**: In your Google Cloud project, go to "APIs & Services" > "Library" and enable the [Google Calendar API](https://console.cloud.google.com/apis/library/calendar-json.googleapis.com).

3. **Create OAuth 2.0 Credentials**: 
   - Go to "APIs & Services" > "Credentials"
   - Click "Create Credentials" > "OAuth client ID"
   - Choose "Web application" as the application type
   - Add your authorized redirect URIs
   - Use these scopes when requesting authorization:
     - `https://www.googleapis.com/auth/calendar.readonly` - To read calendar and event data
     - `https://www.googleapis.com/auth/userinfo.email` - To get the user's email address (optional but recommended)

4. **OAuth Consent Screen**: 
   - Configure your OAuth consent screen in "APIs & Services" > "OAuth consent screen"
   - Google will need to approve your application before external users can authorize it. See [here](https://developers.google.com/identity/protocols/oauth2/production-readiness/sensitive-scope-verification) for more information
   - Until your app is approved, only users on your test users list can authorize it. To edit the test users list, go to "OAuth Consent Screen" > "Test Users"

### Important Notes for Google OAuth

- **Refresh Token Requirement**: Calendar connections require offline access. When implementing the OAuth flow, you **must** include:
  - `access_type=offline` parameter
  - `prompt=consent` parameter (to force the consent screen and ensure you get a refresh token)
- Without a refresh token, the connection will expire after ~1 hour and cannot be renewed

## Create Microsoft Calendar OAuth Application

You'll need to create a Microsoft Azure AD application that users can authorize to access their calendar. You can skip this step if your application won't support Microsoft Calendar. We recommend creating separate applications for development and production.

### Steps

1. **Register an Azure AD Application**: Follow the directions [here](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app) to create a new Microsoft Azure Active Directory application. When it asks you to choose "Supported account types", select "Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)".

2. **Configure API Permissions**: 
   - Go to "API permissions" in your Azure AD app
   - Add these delegated permissions:
     - `Calendars.Read` - To read calendar and event data
     - `User.Read` - To get the user's profile information (optional but recommended)
   - Click "Add a permission" > "Microsoft Graph" > "Delegated permissions"

3. **Publisher Verification** (Optional but Recommended):
   - Microsoft can verify your application before external users can authorize it. See [here](https://learn.microsoft.com/en-us/entra/identity-platform/publisher-verification-overview) for more information
   - This process is automated and should take less than an hour
   - Steps to get verified:
     - [Join the Microsoft AI Cloud Partner Program](https://partner.microsoft.com/en-us/partnership)
     - [Configure your app's publisher domain](https://learn.microsoft.com/en-us/entra/identity-platform/howto-configure-publisher-domain)
     - [Mark your app as publisher verified](https://learn.microsoft.com/en-us/entra/identity-platform/mark-app-as-publisher-verified)

### Important Notes for Microsoft OAuth

- **Refresh Token Requirement**: Calendar connections require offline access. When implementing the OAuth flow, you **must** include:
  - `offline_access` scope in your OAuth request
- Without a refresh token, the connection will expire after ~1 hour and cannot be renewed
- **Tenant ID**: For Microsoft, you'll need to provide the Azure AD tenant ID. You can find this in Azure Portal > Azure Active Directory > Overview. You can also use `common`, `organizations`, or `consumers` for multi-tenant scenarios

## Implement OAuth Flow

You'll need to add code to handle the OAuth flow for users to authorize your Calendar OAuth applications. The flow is essentially the same for both Google and Microsoft:

1. **Add an authorization endpoint**: Redirect users to the OAuth provider's authorization URL
2. **Add a callback endpoint**: Handle the OAuth callback and exchange the authorization code for tokens
3. **Exchange authorization code for refresh token**: In your callback endpoint, exchange the authorization code for an access token and refresh token
4. **Create calendar connection**: After obtaining the refresh token, make a `POST /v2/calendars` request to create the calendar connection, passing:
   - `oauth_client_id`: Your OAuth client ID
   - `oauth_client_secret`: Your OAuth client secret
   - `oauth_refresh_token`: The refresh token obtained from the user's authorization
   - `oauth_tenant_id`: (Microsoft only) The Azure AD tenant ID
   - `raw_calendar_id`: The calendar ID to connect (use `POST /v2/calendars/list-raw` to get available calendars)

## Supported Platforms

- **Google Calendar**: Full support for Google Workspace and personal accounts
- **Microsoft Outlook**: Full support for Microsoft 365 and personal accounts

## Calendar Events

Once connected, calendar events are automatically synced. You can:

- List all calendars
- List events for a calendar
- Get event details
- Schedule bots for specific events or entire event series

## Scheduling Bots for Calendar Events

To schedule a bot for a calendar event:

```bash
curl -X POST "https://api.meetingbaas.com/v2/calendars/CALENDAR-ID/bots" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "event_id": "EVENT-ID",
           "all_occurrences": false,
           "bot_name": "AI Notetaker",
           "recording_mode": "speaker_view"
         }'
```

**Options:**
- `event_id`: The specific event instance ID
- `all_occurrences`: Set to `true` to schedule for all occurrences of a recurring event
- `series_id`: Use this instead of `event_id` to schedule for an entire series

## Shared Service Account Use Case (Invite-To-Schedule)

Once the OAuth flow is configured, and if you want to avoid creating calendar connections for every end-user, you can connect a single dedicated "bot mailbox" (for example, `recording@xyz.io`) and reuse it.

<Callout type="info">
  Meeting BaaS v2 uses a bring-your-own-credentials model and requires OAuth refresh tokens obtained via a normal OAuth consent flow. See [Implement OAuth Flow](#implement-oauth-flow) above. The practical workaround for a "service account" is to use a dedicated mailbox/user account that can complete OAuth once, then reuse its refresh token.
</Callout>

### How it works

1. Create a dedicated provider account and calendar (the bot mailbox).
2. Run a single OAuth consent flow for the bot mailbox to obtain its refresh token, then connect that calendar to Meeting BaaS v2 using `POST /v2/calendars` (you'll get one `calendar_id` for this shared connection).
3. When you need a bot, create the meeting/event and invite the bot mailbox. Make sure the event contains a meeting URL (since bot scheduling depends on it).
4. Meeting BaaS syncs the event into that connection and emits webhooks for the connection and subsequent event changes.
5. In your webhook handler, schedule the bot for the relevant `event_id` using `POST /v2/calendars/{calendar_id}/bots`. For recurring meetings, prefer `series_id` or `all_occurrences`.

## Webhooks

Calendar integrations trigger webhook events for:
- **Connection changes**: When a calendar connection is created, updated, or has a status change
- **Initial sync**: A `calendar.events_synced` webhook is sent once when a calendar is first connected, containing all events within the 30-day window
- **Event changes**: Individual webhooks for event creation, updates, and cancellations (sent for all subsequent changes after initial sync)

### Initial Sync Webhook

When a calendar connection is first created, you'll receive a single `calendar.events_synced` webhook containing all events within the 30-day materialization window. This webhook is sent **only once** and will not be triggered again for:
- Subsequent syncs via push notifications
- Manual resyncs
- Periodic background syncs

**How to use it:**

You have two options for handling the initial event data:

1. **Use the webhook payload**: The `calendar.events_synced` webhook contains the complete event data, which you can use for initial reconciliation
2. **Call the API endpoints**: Alternatively, you can call the `GET /v2/calendars/{calendar_id}/events` or `GET /v2/calendars/{calendar_id}/series` endpoints to fetch the initial event data

Most applications use the API endpoints for initial reconciliation, as they may already be calling these endpoints for other purposes.

See the [Webhooks documentation](/docs/api-v2/webhooks) for details on all calendar webhook events and their payloads.

## FAQ

<Accordions type="single">

<Accordion
  title="What happens if a calendar event is updated close to its start time?"
>

**Lock Window Behavior (4 minutes before event start):**

When an event is updated within 4 minutes of its start time, the system enters a "lock window" where the original bot schedule is preserved to prevent disruption. Here's what happens:

- **Original bot continues**: The bot that was already scheduled will still attempt to join using the original meeting details
- **New bot is also created**: A second bot schedule is created with the updated event details
- **Why this happens**: The original bot may have already been queued for processing before the event update occurred

**Outside Lock Window (more than 4 minutes before start):**

If an event is updated more than 4 minutes before its start time, the bot schedule is safely updated with the new event details, and only one bot will join.

</Accordion>

<Accordion
  title="What happens if a calendar event is deleted close to its start time?"
>

When a calendar event is deleted, the bot schedule is automatically cancelled. If a bot has already been spawned for the event, it will be stopped — the bot will abort before joining or leave the meeting if it's already in.

No tokens are consumed if the bot hadn't started recording yet.

</Accordion>

<Accordion
  title="What happens if the meeting URL is removed from an event?"
>

**If the event originally had a meeting URL:**
- The bot will use the meeting URL from the bot configuration, which was captured when the bot was scheduled
- The bot will still attempt to join even if the URL is later removed from the calendar event

**If the event never had a meeting URL:**
- No bot schedule is created
- Calendar events without meeting URLs are skipped during bot scheduling

</Accordion>

<Accordion title="How often are calendar events synced?">

Calendar events are synced via **push notifications (real-time)**:
- **Google Calendar**: Push notifications via watch channels (renewed every 7 days)
- **Microsoft Calendar**: Push notifications via subscriptions (renewed every 2 days)
- Changes are typically reflected within seconds

</Accordion>

<Accordion
  title="What is the event materialization window?"
>

Meeting BaaS maintains a **30-day rolling window** of calendar events:
- Events are synced from **now** to **30 days in the future**
- Events outside this window are not stored or monitored
- As time progresses, new events enter the window and old events are removed

</Accordion>

<Accordion title="How are recurring events handled?">

**Series-Level Bot Scheduling:**
- You can schedule a bot for all occurrences of a recurring event using `all_occurrences: true` or by providing the `series_id`
- When scheduled at the series level, bots are automatically created for:
  - All existing instances within the 30-day window
  - New instances as they enter the window

**Series Invalidation:**

In some cases, the calendar platform may invalidate an event series (such as when the recurrence pattern changes, an event is moved to a significantly different date, etc.). When this happens:
- All instances of the old series are cancelled
- A new series is created with the updated details
- If series-level bot scheduling was enabled, bots are automatically scheduled for the new series

This behavior is inline with how calendar platforms handle major changes to recurring events.

</Accordion>

<Accordion title="What happens if I decline a calendar event?">

If you decline a calendar event (as the calendar owner):
- The event is treated as **cancelled** in Meeting BaaS
- No bot will be scheduled for declined events
- Existing bot schedules for declined events are automatically cancelled

</Accordion>

<Accordion title="Can I schedule bots for all-day events?">

All-day events are synced and stored, but:
- They typically don't have meeting URLs
- Bots are only scheduled for events with valid meeting URLs
- All-day events without meeting URLs are skipped during bot scheduling

</Accordion>

<Accordion
  title="What meeting platforms are supported?"
>

Meeting BaaS automatically detects meeting URLs for:
- **Zoom** (`zoom.us`)
- **Google Meet** (`meet.google.com`)
- **Microsoft Teams** (`teams.microsoft.com`)
- **Other platforms**: URLs are stored but may not be automatically detected

The meeting platform is detected from:
- The event's meeting URL field
- The event description (for embedded links)
- Conference data (Google Calendar)

</Accordion>

<Accordion
  title="How are event exceptions handled?"
>

**Event exceptions** are recurring event instances that have been modified:
- Modified start time
- Changed title, description, or location
- Different meeting URL

Exceptions are:
- Tracked with an `is_exception: true` flag
- Synced and stored separately from the series pattern
- Handled correctly for bot scheduling

</Accordion>

<Accordion title="What if my OAuth credentials expire?">

**Refresh Token Expiration:**
- Google: Refresh tokens don't expire unless revoked by the user
- Microsoft: Refresh tokens are valid for 90 days but are automatically renewed with each use

**If credentials become invalid:**
- The calendar connection status changes to `error` or `revoked`
- You'll receive a webhook notification
- Users must re-authorize your application to restore the connection

</Accordion>

<Accordion
  title="How do I handle calendar connection errors?"
>

Monitor the `status` field on calendar connections:
- `active`: Connection is working normally
- `error`: Temporary error (e.g., sync failure) - may recover automatically
- `revoked`: User revoked access - requires re-authorization
- `permission_denied`: Missing required permissions - check OAuth scopes

You'll receive webhook notifications for connection status changes.

</Accordion>

<Accordion
  title="Can I connect multiple calendars from the same account?"
>

Yes! You can create separate calendar connections for:
- Multiple calendars from the same Google account
- Multiple calendars from the same Microsoft account
- Primary calendar + shared calendars

Each connection is independent and has its own:
- Sync status
- Bot schedules
- Webhook events

</Accordion>
</Accordions>



---

## Getting the data

Learn how to retrieve meeting recordings, transcriptions, and other data from bots

### Source: ./content/docs/api-v2/getting-started/getting-the-data.mdx


Once a bot completes recording a meeting, you can retrieve the meeting data including recordings, transcriptions, and metadata.

## Getting Bot Details

To get all information about a bot, including artifact URLs:

```bash
curl -X GET "https://api.meetingbaas.com/v2/bots/BOT-ID" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"
```

**Response:**

```json
{
  "success": true,
  "data": {
    "bot_id": "123e4567-e89b-12d3-a456-426614174000",
    "status": "completed",
    "meeting_url": "https://meet.google.com/...",
    "video": "https://s3.amazonaws.com/.../video.mp4",
    "audio": "https://s3.amazonaws.com/.../audio.mp3",
    "transcription": "https://s3.amazonaws.com/.../transcription.json",
    "diarization": "https://s3.amazonaws.com/.../diarization.json",
    "participants": [
      { "name": "Alice", "id": 1, "display_name": "Alice", "profile_picture": "https://..." },
      { "name": "Bob", "id": 2 }
    ],
    "speakers": [
      { "name": "Alice", "id": 1, "display_name": "Alice", "profile_picture": "https://..." },
      { "name": "Bob", "id": 2 }
    ],
    "duration_seconds": 3600,
    "created_at": "2025-01-15T10:00:00Z",
    "updated_at": "2025-01-15T11:00:00Z"
  }
}
```

## Getting Bot Status

For a lightweight status check:

```bash
curl -X GET "https://api.meetingbaas.com/v2/bots/BOT-ID/status" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"
```

**Response:**

```json
{
  "success": true,
  "data": {
    "bot_id": "123e4567-e89b-12d3-a456-426614174000",
    "status": "in_call_recording",
    "transcription_status": "processing",
    "updated_at": "2025-01-15T10:30:00Z"
  }
}
```

## Getting Screenshots

To get screenshots taken during the meeting:

```bash
curl -X GET "https://api.meetingbaas.com/v2/bots/BOT-ID/screenshots" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"
```

**Note:** Screenshots are only available for Google Meet and Microsoft Teams. Zoom does not support screenshots.

## Artifact URLs

All artifact URLs (video, audio, transcription, diarization) are **presigned S3 URLs** that are valid for **4 hours**. Make sure to download them within this time window.

For detailed information about each artifact type, their formats, and use cases, see the [Artifacts documentation](/docs/api-v2/artifacts).

## Recommended Approach

Instead of polling the API, we recommend:

1. **Use webhooks**: Configure webhooks in your account settings to receive `bot.completed` events automatically
2. **Use callbacks**: Provide a `callback_config` when creating the bot to receive notifications for that specific bot
3. **Poll only when necessary**: If you must poll, use a judicious interval (e.g., every 5-10 minutes) and only for reconciliation purposes

For more details, see the [Webhooks documentation](/docs/api-v2/webhooks).


---

## Removing a bot

Learn how to remove or delete bots from meetings

### Source: ./content/docs/api-v2/getting-started/removing-a-bot.mdx


You can remove a bot from a meeting or delete bot data using the v2 API. There are two operations:

1. **Leave meeting**: Instruct a bot to leave the meeting immediately (while it's active)
2. **Delete data**: Permanently delete a bot and all its data (after it's completed or failed)

## Leave Meeting

To instruct a bot to leave the meeting immediately:

```bash
curl -X POST "https://api.meetingbaas.com/v2/bots/BOT-ID/leave" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"
```

**Response:**
```json
{
  "success": true,
  "data": {
    "message": "Bot leave request sent successfully."
  }
}
```

### When Can You Leave a Bot?

The leave endpoint works for bots in any active (non-terminal) state:

- `queued`: Bot hasn't started joining yet
- `pickup_delayed`: Bot has stayed in the `queued` status longer than the expected pickup window
- `joining_call`: Bot is attempting to join the meeting
- `in_waiting_room`: Bot is waiting in the meeting's waiting room
- `in_call_not_recording`: Bot is in the meeting but not recording
- `in_call_recording`: Bot is actively recording
- `recording_paused`: Bot recording is paused
- `recording_resumed`: Bot recording has resumed

It also works for **scheduled bots** that haven't spawned yet — the scheduled bot will be cancelled atomically.

### Error Responses

**404 Not Found:**
```json
{
  "success": false,
  "error": "Not Found",
  "message": "Bot with ID 'BOT-ID' not found",
  "code": "FST_ERR_BOT_NOT_FOUND_BY_ID",
  "statusCode": 404
}
```

**409 Conflict (Bot status doesn't allow leaving):**
```json
{
  "success": false,
  "error": "Conflict",
  "message": "Status of bot 'BOT-ID' is: completed. Operation not permitted in this state.",
  "code": "FST_ERR_BOT_STATUS",
  "statusCode": 409
}
```

This error occurs when the bot is in a terminal status:
- `completed`: Bot has already completed
- `failed`: Bot has already failed

### How It Works

When you call the leave endpoint:
1. The stop signal is delivered to the bot process asynchronously with retries
2. If the bot hasn't started yet (e.g., still `queued`), it will check for pending stop requests on startup and abort before joining the meeting
3. **Pre-recording stops** (bot was in `queued`, `joining_call`, `in_waiting_room`, or `in_call_not_recording`): The bot exits with an `EXITING_MEETING_BEFORE_RECORD` error code. No tokens are consumed.
4. **Recording stops** (bot was in `in_call_recording`, `recording_paused`, or `recording_resumed`): The bot stops recording and transitions to `completed` status. Tokens are consumed based on recording duration.
5. A final webhook event will be sent when the bot finishes processing.

## Delete Bot Data

To permanently delete a bot and all its associated data (recordings, transcriptions, etc.):

```bash
curl -X DELETE "https://api.meetingbaas.com/v2/bots/BOT-ID/delete-data" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"
```

**Response:**
```json
{
  "success": true,
  "data": {
    "bot_id": "123e4567-e89b-12d3-a456-426614174000",
    "deleted": true
  }
}
```

### When Can You Delete Bot Data?

The delete endpoint can only be called when the bot is in one of these statuses:

- `completed`: Bot has successfully completed recording and processing
- `failed`: Bot has failed

### Error Responses

**404 Not Found:**
```json
{
  "success": false,
  "error": "Not Found",
  "message": "Bot with ID 'BOT-ID' not found",
  "code": "FST_ERR_BOT_NOT_FOUND_BY_ID",
  "statusCode": 404
}
```

**409 Conflict (Bot status doesn't allow deletion):**
```json
{
  "success": false,
  "error": "Conflict",
  "message": "Status of bot 'BOT-ID' is: in_call_recording. Operation not permitted in this state.",
  "code": "FST_ERR_BOT_STATUS",
  "statusCode": 409
}
```

This error occurs when the bot is still active (e.g., `in_call_recording`, `transcribing`, etc.). You must wait for the bot to complete or fail, or use the leave endpoint first.

**Note:** This permanently deletes the bot and all its data. This action cannot be undone.

## Cancel Scheduled Bot

To cancel a scheduled bot:

```bash
curl -X DELETE "https://api.meetingbaas.com/v2/bots/scheduled/SCHEDULED-BOT-ID" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"
```

This can be called at any time before the bot reaches a terminal state (`cancelled`, `completed`, or `failed`). If the bot hasn't been spawned yet, the scheduled bot record is cancelled atomically. If the scheduling cron has already spawned the bot, the stop request is persisted and delivered to the bot process. If the bot hadn't started recording, it will exit with `EXITING_MEETING_BEFORE_RECORD` and no tokens are consumed. If recording was already in progress, the bot stops recording normally and tokens are consumed up to the stop time.

**Note:** You can also use the leave endpoint (`POST /v2/bots/:bot_id/leave`) with the scheduled bot's UUID to achieve the same result.

## Important Notes

- Deleting a bot's data removes all associated data including recordings, transcriptions, and screenshots
- Deleted data cannot be recovered
- If a bot is currently recording, leaving it will stop the recording. Use the delete data endpoint afterward to remove artifacts.
- Scheduled and calendar bots can be cancelled at any time — if a bot has already been spawned, it will be stopped automatically



---

## Sending a bot

Learn how to send bots to meetings using the Meeting BaaS v2 API

### Source: ./content/docs/api-v2/getting-started/sending-a-bot.mdx


You can send a bot to a meeting in two ways:

1. **Immediate**: The bot joins the meeting right away
2. **Scheduled**: The bot joins at a specific time in the future

## Immediate Bot

Send a POST request to `https://api.meetingbaas.com/v2/bots`:

<Tabs items={['Bash', 'Python', 'JavaScript']}>
  <Tab value="Bash">
    ```bash
    curl -X POST "https://api.meetingbaas.com/v2/bots" \
         -H "Content-Type: application/json" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY" \
         -d '{
               "meeting_url": "https://meet.google.com/abc-defg-hij",
               "bot_name": "AI Notetaker",
               "recording_mode": "speaker_view",
               "transcription_enabled": true,
               "transcription_config": {
                 "provider": "gladia"
               }
             }'
    ```
  </Tab>
  <Tab value="Python">
    ```python
    import requests

    url = "https://api.meetingbaas.com/v2/bots"
    headers = {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
    }
    data = {
        "meeting_url": "https://meet.google.com/abc-defg-hij",
        "bot_name": "AI Notetaker",
        "recording_mode": "speaker_view",
        "transcription_enabled": true,
        "transcription_config": {
            "provider": "gladia"
        }
    }
    response = requests.post(url, json=data, headers=headers)
    print(response.json())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript
    fetch("https://api.meetingbaas.com/v2/bots", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
      },
      body: JSON.stringify({
        meeting_url: "https://meet.google.com/abc-defg-hij",
        bot_name: "AI Notetaker",
        recording_mode: "speaker_view",
        transcription_enabled: true,
        transcription_config: {
          provider: "gladia"
        }
      }),
    })
      .then((response) => response.json())
      .then((data) => console.log(data.data.bot_id))
      .catch((error) => console.error("Error:", error));
    ```
  </Tab>
</Tabs>

## Scheduled Bot

To schedule a bot to join at a specific time, use `POST /v2/bots/scheduled`:

<Tabs items={['Bash', 'Python', 'JavaScript']}>
  <Tab value="Bash">
    ```bash
    curl -X POST "https://api.meetingbaas.com/v2/bots/scheduled" \
         -H "Content-Type: application/json" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY" \
         -d '{
               "meeting_url": "https://meet.google.com/abc-defg-hij",
               "bot_name": "AI Notetaker",
               "recording_mode": "speaker_view",
               "join_at": "2025-01-20T14:00:00Z"
             }'
    ```
  </Tab>
  <Tab value="Python">
    ```python
    import requests
    from datetime import datetime

    url = "https://api.meetingbaas.com/v2/bots/scheduled"
    headers = {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
    }
    data = {
        "meeting_url": "https://meet.google.com/abc-defg-hij",
        "bot_name": "AI Notetaker",
        "recording_mode": "speaker_view",
        "join_at": "2025-01-20T14:00:00Z"  # ISO 8601 format
    }
    response = requests.post(url, json=data, headers=headers)
    print(response.json())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript
    fetch("https://api.meetingbaas.com/v2/bots/scheduled", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
      },
      body: JSON.stringify({
        meeting_url: "https://meet.google.com/abc-defg-hij",
        bot_name: "AI Notetaker",
        recording_mode: "speaker_view",
        join_at: "2025-01-20T14:00:00Z"  // ISO 8601 format
      }),
    })
      .then((response) => response.json())
      .then((data) => console.log(data.data.bot_id))
      .catch((error) => console.error("Error:", error));
    ```
  </Tab>
</Tabs>

## Request Parameters

### Required Parameters

- `meeting_url`: The meeting URL (Google Meet, Microsoft Teams, or Zoom)
- `bot_name`: The display name of the bot

### Recording Options

- `recording_mode`: One of:
  - `"speaker_view"` (default): Shows only the active speaker
  - `"gallery_view"`: Shows all participants
  - `"audio_only"`: Audio recording only (MP3)

### Bot Appearance

- `bot_image`: Optional. URL to the bot's avatar image (JPEG or PNG, HTTPS required)

### Transcription

- `transcription_enabled`: Set to `true` to enable transcription
- `transcription_config`: Required if `transcription_enabled` is `true`:
  - `provider`: `"gladia"` (default), `"deepgram"`, `"assemblyai"`, `"speechmatics"`, or `"soniox"` (plus `"elevenlabs"` for real-time streaming)
  - `api_key`: Optional. Your transcription provider API key (for BYOK transcription)
  - `custom_params`: Optional. Custom parameters for the transcription provider

### Callbacks

- `callback_enabled`: Set to `true` to enable callbacks for this bot
- `callback_config`: Required if `callback_enabled` is `true`:
  - `url`: The URL to receive callback notifications
  - `method`: `"POST"` (default) or `"PUT"`
  - `secret`: Optional. Secret key included in `x-mb-secret` header for verification

### Timeouts

- `timeout_config`: Optional object:
  - `waiting_room_timeout`: Seconds to wait in waiting room (default: 600, min: 120, max: 1800)
  - `no_one_joined_timeout`: Seconds to wait if no one joins (default: 600, min: 120, max: 1800)
  - `silence_timeout`: Once a participant has been identified, no_one_joined_timeout stops and silence_timeout kicks in. When there is continued silence for the seconds provided, the bot leaves the meeting (default: 600, min: 300, max: 3600)
  - `everyone_left_timeout`: Seconds the bot stays once every other participant has left, then it leaves with `ALL_PARTICIPANTS_LEFT` (default: 30, min: 10, max: 1800). Participants listed in `ignored_participant_names` are not counted. The bot starts checking for an empty meeting 5 minutes into the recording.

<Callout type="info">
Zoom bots that join with Zoom credentials (`zoom_config`) use `waiting_room_timeout`, `no_one_joined_timeout` (only when nobody else ever joins) and `everyone_left_timeout`, but not `silence_timeout`. Zoom bots that join without credentials cannot see who is in the meeting, so they ignore `everyone_left_timeout` and leave an empty meeting through `silence_timeout`.
</Callout>

### Advanced Options

- `allow_multiple_bots`: `true` (default) to allow multiple bots in the same meeting, `false` to prevent duplicates
- `ignored_participant_names`: Optional list of participant names to ignore when evaluating auto-leave conditions. Useful when multiple bots may join the same meeting (e.g., sandbox and staging bots) - each bot can then correctly detect when human participants have left instead of staying because it sees the other bot
- `entry_message`: Optional message the bot sends when joining
- `extra`: Optional custom metadata (included in webhooks and callbacks)
- `streaming_enabled`: Enable audio streaming
- `streaming_config`: Required if `streaming_enabled` is `true`

### Scheduled Bot Specific

- `join_at`: Required for scheduled bots. ISO 8601 timestamp when the bot should join

## Response

The API returns the bot ID:

```json
{
  "success": true,
  "data": {
    "bot_id": "123e4567-e89b-12d3-a456-426614174000"
  }
}
```

For scheduled bots, the `bot_id` is returned immediately and will be reused when the bot actually joins the meeting.

## Next Steps

- [Get meeting data](/docs/api-v2/getting-started/getting-the-data)
- [Set up webhooks](/docs/api-v2/webhooks) for real-time notifications
- [Remove a bot](/docs/api-v2/getting-started/removing-a-bot)



---

## Setting up webhooks

Learn how to configure webhooks to receive real-time notifications

### Source: ./content/docs/api-v2/getting-started/webhooks.mdx


Webhooks allow you to receive real-time notifications about bot and calendar events without polling the API.

## Overview

Meeting BaaS v2 uses [SVIX](https://www.svix.com/) for reliable webhook delivery. Webhooks are configured at the account level and will receive notifications for all events.

## Configuring Webhooks

Webhooks are configured through your account settings (not via the API). Once configured, you'll receive webhook events for:

- Bot status changes
- Bot completion
- Bot failures
- Calendar events (connections, syncs, event changes)

## Webhook Events

### Bot Events

- `bot.status_change`: Triggered when a bot's status changes
- `bot.completed`: Triggered when a bot successfully completes
- `bot.failed`: Triggered when a bot fails

### Calendar Events

- `calendar.connection_created`: New calendar connection created
- `calendar.connection_updated`: Calendar connection updated
- `calendar.connection_deleted`: Calendar connection deleted
- `calendar.connection_error`: Calendar connection error
- `calendar.events_synced`: Calendar events synced - When a calendar syncs for the first time
- `calendar.event_created`: New calendar event created
- `calendar.event_updated`: Calendar event updated
- `calendar.event_cancelled`: Calendar event cancelled

For detailed information about each event type, see the [Webhooks documentation](/docs/api-v2/webhooks).

## Webhook Security

All webhooks are signed using SVIX's signature verification. Verify webhooks using:

- `svix-id`: Unique message ID
- `svix-timestamp`: Timestamp of the message
- `svix-signature`: Signature for verification

Use SVIX's verification libraries to verify webhook signatures in your code.

## Callbacks

In addition to account-level webhooks, you can also configure **callbacks** per-bot when creating a bot. Callbacks are direct HTTP requests sent to a URL you specify, and are only sent for `bot.completed` and `bot.failed` events.

See the [Webhooks documentation](/docs/api-v2/webhooks) for more details on callbacks.



---

## Getting the Data

Learn how to receive meeting data through webhooks

### Source: ./content/docs/api/getting-started/getting-the-data.mdx


# Getting Meeting Data

Your webhook URL will receive two types of data:

1. Live meeting events during the meeting
2. Final meeting data after completion

These events will start flowing in after [sending a bot to a meeting](/docs/api/getting-started/sending-a-bot).

## 1. Live Meeting Events

```http
POST /your-endpoint
x-meeting-baas-api-key: YOUR-API-KEY

{
  "event": "bot.status_change",
  "data": {
    "bot_id": "123e4567-e89b-12d3-a456-426614174000",
    "status": {
      "code": "joining_call",
      "created_at": "2024-01-01T12:00:00.000Z"
    }
  }
}
```

### Status Event Fields

- `event`: The key-value pair for bot status events. Always `bot.status_change`.
- `data.bot_id`: The identifier of the bot.
- `data.status.code`: The code of the event. One of:
  - `joining_call`: The bot has acknowledged the request to join the call.
  - `in_waiting_room`: The bot is in the "waiting room" of the meeting.
  - `in_call_not_recording`: The bot has joined the meeting, however it is not recording yet.
  - `in_call_recording`: The bot is in the meeting and recording the audio and video.
  - `recording_paused`: The recording has been temporarily paused.
  - `recording_resumed`: The recording has resumed after being paused.
  - `call_ended`: The bot has left the call.
  - `bot_rejected`: The bot was rejected from joining the meeting.
  - `bot_removed`: The bot was removed from the meeting.
  - `waiting_room_timeout`: The bot timed out while waiting to be admitted.
  - `invalid_meeting_url`: The provided meeting URL was invalid.
  - `meeting_error`: An unexpected error occurred during the meeting.
- `data.status.created_at`: An ISO string of the datetime of the event.

When receiving an `in_call_recording` event, additional data is provided:

- `data.status.start_time`: The timestamp when the recording started.

For `meeting_error` events, additional error details are provided:

- `data.status.error_message`: A description of the error that occurred.
- `data.status.error_type`: The type of error encountered.

## 2. Final Meeting Data

You'll receive either a `complete` or `failed` event.

### Success Response (`complete`)

```http
POST /your-endpoint
x-meeting-baas-api-key: YOUR-API-KEY

{
  "event": "complete",
  "data": {
    "bot_id": "123e4567-e89b-12d3-a456-426614174000",
    "mp4": "https://bots-videos.s3.eu-west-3.amazonaws.com/path/to/video.mp4?X-Amz-Signature=...",
    "speakers": ["Alice", "Bob"],
    "transcript": [{
      "speaker": "Alice",
      "words": [{
        "start": 1.3348110430839002,
        "end": 1.4549110430839003,
        "word": "Hi"
      }, {
        "start": 1.4549110430839003,
        "end": 1.5750110430839004,
        "word": "Bob!"
      }]
    }, {
      "speaker": "Bob",
      "words": [{
        "start": 2.6583010430839,
        "end": 2.778401043083901,
        "word": "Hello"
      }, {
        "start": 2.778401043083901,
        "end": 2.9185110430839005,
        "word": "Alice!"
      }]
    }]
  }
}
```

<Callout type="warn" icon={<AlertTriangle className="h-5 w-5" />}>
  **IMPORTANT**: The mp4 URL is a pre-signed AWS S3 URL that is only valid for 2
  hours. Make sure to download the recording promptly or generate a new URL
  through the API if needed.
</Callout>

#### Complete Response Fields

- `bot_id`: The identifier of the bot.
- `mp4`: A private AWS S3 URL of the mp4 recording of the meeting. Valid for two hours only.
- `speakers`: The list of speakers in this meeting. Currently requires transcription to be enabled.
- `transcript` (optional): The meeting transcript. Only given when `speech_to_text` is set when asking for a bot. An array containing:
  - `transcript.speaker`: The speaker name.
  - `transcript.words`: The list of words, each containing:
    - `transcript.words.start`: The start time of the word
    - `transcript.words.end`: The end time of the word
    - `transcript.words.word`: The word itself

### Failure Response (`failed`)

```http
POST /your-endpoint
x-meeting-baas-api-key: YOUR-API-KEY

{
  "event": "failed",
  "data": {
    "bot_id": "123e4567-e89b-12d3-a456-426614174000",
    "error": "CannotJoinMeeting"
  }
}
```

### Error Types

| Error                 | Description                                                                                                                                                                                                    |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| CannotJoinMeeting     | The bot could not join the meeting URL provided. In most cases, this is because the meeting URL was only accessible for logged-in users invited to the meeting.                                                |
| TimeoutWaitingToStart | The bot has quit after waiting to be accepted. By default this is 10 minutes, configurable via `automatic_leave.waiting_room_timeout` or `automatic_leave.noone_joined_timeout` (both default to 600 seconds). |
| BotNotAccepted        | The bot has been refused in the meeting.                                                                                                                                                                       |
| BotRemoved            | The bot was removed from the meeting by a participant.                                                                                                                                                         |
| InternalError         | An unexpected error occurred. Please contact us if the issue persists.                                                                                                                                         |
| InvalidMeetingUrl     | The meeting URL provided is not a valid (Zoom, Meet, Teams) URL.                                                                                                                                               |

### Recording End Reasons

| Reason            | Description                                                     |
| ----------------- | --------------------------------------------------------------- |
| bot_removed       | Bot removed by participant                                      |
| no_attendees      | No participants present                                         |
| no_speaker        | Extended silence                                                |
| recording_timeout | Maximum duration reached                                        |
| api_request       | Bot [removed via API](/docs/api/getting-started/removing-a-bot) |
| meeting_error     | An error occurred during the meeting (e.g., connection issues)  |


---

## Removing a Bot

Learn how to remove a bot from an ongoing meeting using the API

### Source: ./content/docs/api/getting-started/removing-a-bot.mdx


# Removing a Bot

## Overview

When you need to end a bot's participation in a meeting, you can use the API to remove it immediately. This is useful for:

- Ending recordings early
- Freeing up bot resources
- Responding to meeting conclusion

## API Request

Send a DELETE request to `https://api.meetingbaas.com/bots/{YOUR_BOT_ID}`:

<Tabs items={['Bash', 'Python', 'JavaScript']}>
  <Tab value="Bash">
    ```bash title="leave_meeting.sh"
    curl -X DELETE "https://api.meetingbaas.com/bots/YOUR_BOT_ID" \
         -H "Content-Type: application/json" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY"
    ```
  </Tab>
  <Tab value="Python">
    ```python title="leave_meeting.py"
    import requests

    bot_id = "YOUR_BOT_ID"
    url = f"https://api.meetingbaas.com/bots/{bot_id}"
    headers = {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
    }

    response = requests.delete(url, headers=headers)
    if response.status_code == 200:
        print("Bot successfully removed from the meeting.")
    else:
        print("Failed to remove the bot:", response.json())
    ```

  </Tab>
  <Tab value="JavaScript">
    ```javascript title="leave_meeting.js"
    const botId = "YOUR_BOT_ID";
    fetch(`https://api.meetingbaas.com/bots/${botId}`, {
      method: "DELETE",
      headers: {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
      },
    })
      .then((response) => {
        if (response.ok) {
          console.log("Bot successfully removed from the meeting.");
        } else {
          console.error("Failed to remove the bot:", response.statusText);
        }
      })
      .catch((error) => console.error("Error:", error));
    ```
  </Tab>
</Tabs>

## Required Parameters

- **Path Parameter**: `bot_id` - The unique identifier received when [sending the bot](/docs/api/getting-started/sending-a-bot)
- **Header**: `x-meeting-baas-api-key` - Your API key for authentication

Both parameters are mandatory for the request to succeed.

## Response

The API will respond with a simple confirmation:

```http
HTTP/2 200
Content-Type: application/json

{ "ok": true }
```

## What Happens Next

When a bot is removed:

1. The bot leaves the meeting immediately
2. A `call_ended` status event is sent to your webhook
3. The final meeting data up to that point is delivered

For more details about these webhook events, see [Getting the Data](/docs/api/getting-started/getting-the-data).


---

## Sending a bot

Learn how to send AI bots to meetings through the Meeting BaaS API, with options for immediate or scheduled joining and customizable settings

### Source: ./content/docs/api/getting-started/sending-a-bot.mdx


# Sending a Bot to a Meeting

You can summon a bot in two ways with a simple curl request, using one our code examples, our more simply if you use Typescript, our [SDK](https://www.npmjs.com/package/@meeting-baas/sdk).

## API Request

Send a POST request to [https://api.meetingbaas.com/bots](https://api.meetingbaas.com/bots):

<Tabs items={['Bash', 'Python', 'JavaScript']}>
  <Tab value="Bash">
    ```bash title="join_meeting.sh"
    curl -X POST "https://api.meetingbaas.com/bots" \
         -H "Content-Type: application/json" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY" \
         -d '{
               "meeting_url": "YOUR-MEETING-URL",
               "bot_name": "AI Notetaker",
               "recording_mode": "speaker_view",
               "bot_image": "https://example.com/bot.jpg",
               "entry_message": "I am a good meeting bot :)",
               "speech_to_text": {
                 "provider": "Default"
               },
               "automatic_leave": {
                 "waiting_room_timeout": 600
               }
             }'
    ```
  </Tab>
  <Tab value="Python">
    ```python title="join_meeting.py"
    import requests
    url = "https://api.meetingbaas.com/bots"
    headers = {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
    }
    config = {
        "meeting_url": "YOUR-MEETING-URL",
        "bot_name": "AI Notetaker",
        "recording_mode": "speaker_view",
        "bot_image": "https://example.com/bot.jpg",
        "entry_message": "I am a good meeting bot :)",
        "speech_to_text": {
            "provider": "Default"
        },
        "automatic_leave": {
            "waiting_room_timeout": 600  # 10 minutes in seconds
        }
    }
    response = requests.post(url, json=config, headers=headers)
    print(response.json())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript title="join_meeting.js"
    fetch("https://api.meetingbaas.com/bots", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
      },
      body: JSON.stringify({
        meeting_url: "YOUR-MEETING-URL",
        bot_name: "AI Notetaker",
        recording_mode: "speaker_view",
        bot_image: "https://example.com/bot.jpg",
        entry_message: "I am a good meeting bot :)",
        speech_to_text: {
          provider: "Default",
        },
        automatic_leave: {
          waiting_room_timeout: 600,
        },
      }),
    })
      .then((response) => response.json())
      .then((data) => console.log(data.bot_id))
      .catch((error) => console.error("Error:", error));
    ```
  </Tab>
</Tabs>

## Request Parameters

### Required Parameters

- `meeting_url`: The meeting URL to join. Accepts Google Meet, Microsoft Teams or Zoom URLs.
- `bot_name`: The display name of the bot.

### Recording Options

- `recording_mode`: Optional. One of:
  - `"speaker_view"`: (default) The recording will only show the person speaking at any time
  - `"gallery_view"`: The recording will show all the speakers
  - `"audio_only"`: The recording will be a mp3

### Bot Appearance and Behavior

- `bot_image`: The URL of the image the bot will display. Must be a valid URI format. Optional.
- `entry_message`: Optional. The message the bot will write within 15 seconds after being accepted in the meeting.

### Transcription Settings

- `speech_to_text`: Optional. If not provided, no transcription will be generated and processing time will be faster.
  - Must be an object with:
    - `provider`: One of:
      - `"Default"`: Standard transcription, no API key needed
      - `"Gladia"` or `"Runpod"`: Requires their respective API key to be provided
    - `api_key`: Required when using Gladia or Runpod providers. Must be a valid API key from the respective service.

### Automatic Leaving

- `automatic_leave`: Optional object containing:
  - `waiting_room_timeout`: Time in seconds the bot will wait in a meeting room before dropping. Default is 600 (10 minutes)
  - `noone_joined_timeout`: Time in seconds the bot will wait if no one joins the meeting

### Advanced Options

- `webhook_url`: URL for webhook notifications
- `deduplication_key`: String for deduplication. By default, Meeting BaaS will reject you sending multiple bots to a same meeting within 5 minutes, to avoid spamming.
- `streaming`: Object containing optional WebSocket streaming configuration:
  - `audio_frequency`: Audio frequency for the WebSocket streams. Can be "16khz" or "24khz" (defaults to "24khz")
  - `input`: WebSocket endpoint to receive raw audio bytes and speaker diarization as JSON strings from the meeting
  - `output`: WebSocket endpoint to stream raw audio bytes back into the meeting, enabling bot speech
- `extra`: Additional custom data
- `start_time`: Unix timestamp (in seconds) for when the bot should join the meeting. For example, if you want the bot to join at exactly 2:00 PM, set this to the millisecond timestamp of 2:00 PM.

## Response

The API will respond with the unique identifier for your bot:

```http
HTTP/2 200
Content-Type: application/json

{
  "bot_id": 42
}
```

## Next Steps

Use this `bot_id` to:

- [Monitor the bot's status and receive meeting data](/docs/api/getting-started/getting-the-data)
- [Remove the bot from the meeting](/docs/api/getting-started/removing-a-bot)


---

## Streaming Meeting Data

Learn how to stream real-time audio and speaker metadata from meetings using WebSocket connections

### Source: ./content/docs/api/getting-started/streaming-meeting-data.mdx


# Streaming Meeting Data

MeetingBaas provides real-time audio streaming through WebSocket connections. This enables you to process meeting audio as it happens, build live transcription systems, or create interactive meeting experiences.

## Overview

When configuring a bot with streaming enabled, MeetingBaas will connect to your WebSocket endpoint and send two types of messages:

1. **Speaker Metadata** (JSON) - Information about who is speaking
2. **Audio Data** (Binary) - Raw PCM audio chunks

## Setting Up Streaming

To enable streaming, include the `streaming` configuration when [sending a bot](/docs/api/getting-started/sending-a-bot):

<Tabs items={['Bash', 'Python', 'JavaScript']}>
  <Tab value="Bash">
    ```bash title="join_meeting_with_streaming.sh"
    curl -X POST "https://api.meetingbaas.com/bots" \
         -H "Content-Type: application/json" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY" \
         -d '{
               "meeting_url": "YOUR-MEETING-URL",
               "bot_name": "Transcription Bot",
               "streaming": {
                 "output": "ws://your-websocket-server:8080",
                 "audio_frequency": "24khz"
               }
             }'
    ```
  </Tab>
  <Tab value="Python">
    ```python title="join_meeting_with_streaming.py"
    import requests

    url = "https://api.meetingbaas.com/bots"
    headers = {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
    }
    config = {
        "meeting_url": "YOUR-MEETING-URL",
        "bot_name": "Transcription Bot",
        "streaming": {
            "output": "ws://your-websocket-server:8080",
            "audio_frequency": "24khz"  # or "16khz"
        }
    }
    response = requests.post(url, json=config, headers=headers)
    print(response.json())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript title="join_meeting_with_streaming.js"
    fetch("https://api.meetingbaas.com/bots", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
      },
      body: JSON.stringify({
        meeting_url: "YOUR-MEETING-URL",
        bot_name: "Transcription Bot",
        streaming: {
          output: "ws://your-websocket-server:8080",
          audio_frequency: "24khz",
        },
      }),
    })
      .then((response) => response.json())
      .then((data) => console.log(data.bot_id))
      .catch((error) => console.error("Error:", error));
    ```
  </Tab>
</Tabs>

## Streaming Configuration

### Required Parameters

- `streaming.output`: Your WebSocket server endpoint URL where MeetingBaas will connect
- `streaming.audio_frequency`: Audio sample rate - either `"16khz"` or `"24khz"` (defaults to `"24khz"`)

### Optional Parameters

- `streaming.input`: WebSocket endpoint to send audio back into the meeting (enables bot speech)

## Message Types

### 1. Speaker Metadata (JSON)

Speaker information is sent as JSON messages when speakers change or their speaking status changes.

**Message Format:**
```json
[
  {
    "name": "John Doe",
    "id": 123,
    "timestamp": 1640995200000,
    "isSpeaking": true
  }
]
```

**Fields:**
- `name`: Speaker's display name
- `id`: Unique speaker identifier  
- `timestamp`: Unix timestamp in milliseconds
- `isSpeaking`: Boolean indicating if the speaker is currently talking

### 2. Audio Data (Binary)

Raw audio chunks are sent as binary buffers containing PCM audio data.

**Characteristics:**
- Format: PCM audio
- Sample Rate: 16kHz or 24kHz (as configured)
- Channels: Mono
- Bit Depth: 16-bit

## Message Processing

To differentiate between speaker metadata and audio data:

1. **Speaker Metadata**: JSON messages that can be parsed and contain speaker information
2. **Audio Data**: Binary data that cannot be parsed as JSON

The key is to attempt JSON parsing on incoming messages. If parsing succeeds and the message contains speaker fields (`name`, `id`, `timestamp`, `isSpeaking`), it's speaker metadata. If parsing fails, it's audio data.

## Common Use Cases

- **Live Transcription**: Process audio chunks in real-time for immediate transcription
- **Speaker Identification**: Track who is speaking and when
- **Audio Analysis**: Perform real-time analysis on meeting audio
- **Interactive Bots**: Send audio back to meetings using the `input` endpoint

## Next Steps

- [Learn about webhook events](/docs/api/getting-started/getting-the-data) for meeting status updates
- [Remove bots from meetings](/docs/api/getting-started/removing-a-bot) when streaming is complete
- Explore [calendar integration](/docs/api/getting-started/calendars) for scheduled meetings


---

## Syncing Calendars

Learn how to sync calendars with the API, and automatically send meeting bots to the right place at the right time

### Source: ./content/docs/api/getting-started/syncing-calendars.mdx


# Calendar Synchronization

<Callout type="info">
  This page has been moved to a new location. You'll be automatically redirected
  to [Calendar Synchronization](/docs/api/getting-started/calendars).
</Callout>

<meta
  http-equiv="refresh"
  content="0;url=/docs/api/getting-started/calendars"
/>

<script>window.location.href = "/docs/api/getting-started/calendars";</script>

Meeting BaaS allows you to automatically sync calendars from Outlook and Google Workspace to deploy bots to scheduled meetings. This helps you automate recording and participation in meetings without manual intervention.

<div className="grid grid-cols-1 gap-4 mt-6 md:grid-cols-2 lg:grid-cols-3">
  <Card title="1. Calendar Sync Setup" href="/docs/api/getting-started/calendars/setup" icon={<ChevronRight className="w-4 h-4" />}>
    Learn how to authenticate and set up calendar integrations with Google Workspace and Microsoft Outlook
  </Card>

<Card
  title="2. Managing Calendar Events"
  href="/docs/api/getting-started/calendars/events"
  icon={<ChevronRight className="h-4 w-4" />}
>
  Work with calendar events and schedule automated recordings for meetings
</Card>

  <Card title="3. Webhooks & Maintenance" href="/docs/api/getting-started/calendars/webhooks" icon={<ChevronRight className="w-4 h-4" />}>
    Receive real-time updates, handle errors, and maintain your calendar integrations
  </Card>
</div>

## Key Benefits

- **Automated Bot Deployment**: Automatically send bots to meetings as they appear on calendars
- **Multi-Calendar Support**: Connect to both Google Workspace and Microsoft Outlook calendars
- **Real-Time Updates**: Receive webhook notifications when calendar events change
- **Selective Recording**: Apply business logic to determine which meetings to record

## Implementation Overview

1. First, [set up calendar integrations](/docs/api/getting-started/calendars/setup) using OAuth authentication
2. Then, [work with calendar events](/docs/api/getting-started/calendars/events) to schedule automated recordings
3. Finally, [implement webhooks](/docs/api/getting-started/calendars/webhooks) to receive real-time updates and handle maintenance

This modular approach allows you to implement each component at your own pace and focuses the documentation on specific aspects of the integration.


---

## API Reference

Complete API reference for Meeting BaaS v2

### Source: ./content/docs/api-v2/reference/index.mdx


The API reference is automatically generated from the OpenAPI specification. All endpoints are organized by tag (Bots, Calendars, etc.).

## Webhook & Callback Payloads

Reference documentation for all webhook and callback payload structures is available in the [Webhooks & Callbacks](./webhooks) section.



---

## List Bots with Metadata

### Source: ./content/docs/api/reference/bots_with_metadata.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieves a paginated list of the user's bots with essential metadata, including IDs, names, and meeting details. Supports filtering, sorting, and advanced querying options.

<APIPage document={"./openapi.json"} operations={[{"path":"/bots/bots_with_metadata","method":"get"}]} />


---

## Delete Data

### Source: ./content/docs/api/reference/delete_data.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Deletes a bot's data including recording, transcription, and logs. Only metadata is retained. Rate limited to 5 requests per minute per API key.

<APIPage document={"./openapi.json"} operations={[{"path":"/bots/{uuid}/delete_data","method":"post"}]} />


---

## Get Meeting Data

### Source: ./content/docs/api/reference/get_meeting_data.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Get meeting recording and metadata

<APIPage document={"./openapi.json"} operations={[{"path":"/bots/meeting_data","method":"get"}]} />


---

## Get Screenshots

### Source: ./content/docs/api/reference/get_screenshots.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieves screenshots captured during the bot's session

<APIPage document={"./openapi.json"} operations={[{"path":"/bots/{uuid}/screenshots","method":"get"}]} />

---

## Join

### Source: ./content/docs/api/reference/join.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Have a bot join a meeting, now or in the future. You can provide a `webhook_url` parameter to receive webhook events specific to this bot, overriding your account's default webhook URL. Events include recording completion, failures, and transcription updates.

<APIPage document={"./openapi.json"} operations={[{"path":"/bots/","method":"post"}]} />


---

## Leave

### Source: ./content/docs/api/reference/leave.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Leave

<APIPage document={"./openapi.json"} operations={[{"path":"/bots/{uuid}","method":"delete"}]} />


---

## Retranscribe Bot

### Source: ./content/docs/api/reference/retranscribe_bot.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Transcribe or retranscribe a bot's audio using the Default or your provided Speech to Text Provider

<APIPage document={"./openapi.json"} operations={[{"path":"/bots/retranscribe","method":"post"}]} />


---

## Chat MCP Overview

A Comprehensive Guide to Chat MCP - The Standalone Model Context Protocol Server for Meeting BaaS Integration

### Source: ./content/docs/mcp-servers/chat-mcp/overview.mdx


Chat MCP (Model Context Protocol) is a specialized server implementation that bridges AI assistants with Meeting BaaS's powerful chat and meeting capabilities. By implementing the [Model Context Protocol](https://www.anthropic.com/news/model-context-protocol), it enables AI-powered bots to seamlessly participate in meetings, manage calendars, and handle sophisticated meeting operations through a unified, standardized interface.

## Key Features and Capabilities

<Cards>
  <Card title="Meeting Management" icon={<Video className="text-blue-400" />}>
    Comprehensive meeting control including joining, leaving, and real-time presence management. Supports full meeting lifecycle operations and data handling.
  </Card>
  <Card title="Calendar Integration" icon={<Calendar className="text-purple-400" />}>
    Robust calendar synchronization with support for multiple providers. Enables automated meeting scheduling, updates, and calendar event management.
  </Card>
  <Card title="Advanced Bot Management" icon={<Bot className="text-green-400" />}>
    Complete bot lifecycle management including status monitoring, configuration updates, and metadata handling. Supports multiple bot personas and behaviors.
  </Card>
  <Card title="Voice-Enabled AI Bots" icon={<Mic className="text-yellow-400" />}>
    Create and manage AI bots with advanced speech capabilities. Includes customizable voices, speaking patterns, and interactive responses.
  </Card>
  <Card title="Intelligent Event Scheduling" icon={<Clock className="text-amber-400" />}>
    Automated event and recording management with smart scheduling capabilities and conflict resolution.
  </Card>
  <Card title="Development Tools" icon={<Wrench className="text-gray-400" />}>
    Comprehensive testing suite and debugging utilities for development and maintenance. Includes logging, monitoring, and diagnostic tools.
  </Card>
</Cards>

## Technical Prerequisites

1. **Runtime Environment**
   - Node.js: Version 16.x or higher
   - NPM: Latest stable version
   - Operating System: Windows, Linux, or macOS

2. **Network Requirements**
   - Stable internet connection
   - Access to Meeting BaaS API endpoints
   - Firewall rules allowing WebSocket connections

## Account Requirements

1. **Meeting BaaS Account**
   - Active subscription
   - API access enabled
   - Appropriate service tier for intended usage

2. **Authentication**
   - Valid API key from [Meeting BaaS Dashboard](https://meetingbaas.com)
   - Properly configured endpoint access
   - Whitelisted IP addresses (if required)

## Authentication Methods

Chat MCP supports two primary authentication methods:

### 1. Header-based Authentication
```typescript
// Using x-api-key header
headers: {
  'x-api-key': 'YOUR_API_KEY'
}
```

### 2. Parameter-based Authentication
```typescript
// Using WithCredentials variants in tool parameters
{
  credentials: {
    apiKey: 'YOUR_API_KEY'
  }
}
```

## Implementation Guide

### Basic Server Setup

```typescript
import { BaasClient } from "@meeting-baas/sdk";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp";

// Initialize the MCP server
const server = new McpServer();

// Configure your API key (use environment variables in production)
const apiKey = process.env.MEETING_BAAS_API_KEY;

// Initialize the BaaS client
const baasClient = createBaasClient({
  api_key: apiKey
});

// Register required tools and middleware
registerTools(server, apiKey);
```

### Development Workflow

1. **Initial Setup**
   ```bash
   # Install project dependencies
   npm install

   # Build the project
   npm run build

   # Start the development server
   npm run start
   ```

2. **Configuration**
   - Set up environment variables
   - Configure logging levels
   - Set up development tools





---

## Server Configuration

A comprehensive guide to configuring and initializing the Chat MCP server

### Source: ./content/docs/mcp-servers/chat-mcp/server-configuration.mdx


This comprehensive guide covers the setup, configuration, and initialization of the Chat MCP server, including detailed explanations of API handler setup, tool registration, and available capabilities.

## API Handler Setup

The Chat MCP server utilizes a centralized API handler to manage tool registrations and server capabilities. Below is a detailed explanation of how to set up the API handler:

```typescript
import { initializeMcpApiHandler } from "../lib/mcp-api-handler";
import registerTools from "./tools";

const handler = initializeMcpApiHandler(
  // Tool Registration Callback
  (server, apiKey) => {
    // Register Meeting BaaS SDK tools with the provided API key
    server = registerTools(server, apiKey);
  },
  // Server Capabilities Configuration
  {
    capabilities: {
      tools: {
        // Meeting Management Tools
        joinMeeting: {
          description: "Join's a meeting using the MeetingBaas api",
        },
        leaveMeeting: {
          description: "Leave a meeting using the MeetingBaas api",
        },
        getMeetingData: {
          description: "Get meeting data using the MeetingBaas api",
        },
        deleteData: {
          description: "Delete meeting data using the MeetingBaas api",
        },

        // Calendar Management Tools
        createCalendar: {
          description: "Create a calendar using the MeetingBaas api",
        },
        listCalendars: {
          description: "List calendars using the MeetingBaas api",
        },
        getCalendar: {
          description: "Get calendar using the MeetingBaas api",
        },
        deleteCalendar: {
          description: "Delete calendar using the MeetingBaas api",
        },
        updateCalendar: {
          description: "Update calendar using the MeetingBaas api",
        },
        resyncAllCalendars: {
          description: "Resync all calendars using the MeetingBaas api",
        },

        // Bot Management Tools
        botsWithMetadata: {
          description: "Get bots with metadata using the MeetingBaas api",
        },

        // Event Management Tools
        listEvents: {
          description: "List events using the MeetingBaas api",
        },
        scheduleRecordEvent: {
          description: "Schedule a recording using the MeetingBaas api",
        },
        unscheduleRecordEvent: {
          description: "Unschedule a recording using the MeetingBaas api",
        },

        // Speaking Bot Management Tools
        joinSpeakingMeeting: {
          description: "Join a speaking meeting using the MeetingBaas api",
        },
        leaveSpeakingMeeting: {
          description: "Leave a speaking meeting using the MeetingBaas api",
        },

        // Utility Tools
        echo: {
          description: "Echo a message for testing purposes",
        },
      },
    },
  }
);

export default handler;
```

## Server Initialization

The server initialization process consists of three crucial steps:

### 1. Import Required Dependencies

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp";
import { initializeMcpApiHandler } from "../lib/mcp-api-handler";
```

### 2. Create Tool Registration Function

```typescript
function registerTools(server: McpServer, apiKey: string): McpServer {
  // Register tools by category
  server = registerMeetingTools(server, apiKey);
  server = registerCalendarTools(server, apiKey);
  server = registerSpeakingTools(server, apiKey);
  server = registerUtilityTools(server);
  
  return server;
}
```

## Available Capabilities

### Meeting Management

<Card title="Core Meeting Controls" icon="users">
  <div className="space-y-4">
    <div className="border-b pb-4">
      <h4 className="font-semibold text-lg mb-2">Join Meeting (`joinMeeting`)</h4>
      <ul className="list-disc pl-6 space-y-1">
        <li>Enables seamless participant joining across multiple meeting platforms</li>
        <li>Handles secure authentication and connection setup</li>
        <li>Supports various meeting providers (Zoom, Teams, etc.)</li>
      </ul>
    </div>

    <div className="border-b pb-4">
      <h4 className="font-semibold text-lg mb-2">Leave Meeting (`leaveMeeting`)</h4>
      <ul className="list-disc pl-6 space-y-1">
        <li>Provides graceful exit handling from active meetings</li>
        <li>Ensures proper cleanup of session resources</li>
        <li>Manages participant departure notifications</li>
      </ul>
    </div>

    <div className="border-b pb-4">
      <h4 className="font-semibold text-lg mb-2">Meeting Data (`getMeetingData`)</h4>
      <ul className="list-disc pl-6 space-y-1">
        <li>Retrieves comprehensive meeting information and analytics</li>
        <li>Includes detailed participant data and engagement metrics</li>
        <li>Provides meeting duration and technical statistics</li>
      </ul>
    </div>

    <div>
      <h4 className="font-semibold text-lg mb-2">Data Cleanup (`deleteData`)</h4>
      <ul className="list-disc pl-6 space-y-1">
        <li>Manages efficient meeting resource cleanup</li>
        <li>Ensures data privacy and GDPR compliance</li>
        <li>Optimizes storage through automated cleanup processes</li>
      </ul>
    </div>
  </div>
</Card>

### Calendar Management

<Card title="Calendar Operations" icon="calendar">
  <div className="space-y-4">
    <div className="border-b pb-4">
      <h4 className="font-semibold text-lg mb-2">Calendar Creation and Setup</h4>
      <ul className="list-disc pl-6 space-y-1">
        <li>
          Create Calendar (`createCalendar`)
          <ul className="list-circle pl-6 mt-1">
            <li>Set up new calendars with custom configurations</li>
            <li>Define calendar properties and access controls</li>
            <li>Configure timezone and availability settings</li>
          </ul>
        </li>
      </ul>
    </div>

    <div className="border-b pb-4">
      <h4 className="font-semibold text-lg mb-2">Calendar Management Tools</h4>
      <ul className="list-disc pl-6 space-y-2">
        <li>
          List Calendars (`listCalendars`)
          <ul className="list-circle pl-6 mt-1">
            <li>View all available calendars with filtering options</li>
            <li>Sort and organize calendar listings</li>
          </ul>
        </li>
        <li>
          Calendar Details (`getCalendar`)
          <ul className="list-circle pl-6 mt-1">
            <li>Access detailed calendar information and settings</li>
            <li>View calendar permissions and sharing status</li>
          </ul>
        </li>
        <li>
          Calendar Updates (`updateCalendar`)
          <ul className="list-circle pl-6 mt-1">
            <li>Modify existing calendar configurations</li>
            <li>Update access controls and sharing settings</li>
          </ul>
        </li>
      </ul>
    </div>

    <div>
      <h4 className="font-semibold text-lg mb-2">Synchronization Features</h4>
      <ul className="list-disc pl-6 space-y-1">
        <li>
          Full Sync (`resyncAllCalendars`)
          <ul className="list-circle pl-6 mt-1">
            <li>Force synchronization across all connected platforms</li>
            <li>Ensure data consistency and real-time updates</li>
            <li>Resolve conflicts and maintain data integrity</li>
          </ul>
        </li>
      </ul>
    </div>
  </div>
</Card>

### Bot Management

<Card title="Bot Intelligence" icon="robot">
  <div className="space-y-4">
    <div className="border-b pb-4">
      <h4 className="font-semibold text-lg mb-2">Bot Information Management</h4>
      <ul className="list-disc pl-6 space-y-1">
        <li>
          Metadata Access (`botsWithMetadata`)
          <ul className="list-circle pl-6 mt-1">
            <li>Retrieve comprehensive bot information and status</li>
            <li>Access real-time performance analytics</li>
            <li>Monitor bot health and activity metrics</li>
          </ul>
        </li>
      </ul>
    </div>

    <div>
      <h4 className="font-semibold text-lg mb-2">Voice Integration Features</h4>
      <ul className="list-disc pl-6 space-y-2">
        <li>
          Meeting Voice Control
          <ul className="list-circle pl-6 mt-1">
            <li>`joinSpeakingMeeting`: Initialize voice interaction capabilities</li>
            <li>`leaveSpeakingMeeting`: Gracefully terminate voice sessions</li>
            <li>Real-time voice processing and response handling</li>
          </ul>
        </li>
      </ul>
    </div>
  </div>
</Card>

### Event Management

<Card title="Event Orchestration" icon="calendar-check">
  <div className="space-y-4">
    <div className="border-b pb-4">
      <h4 className="font-semibold text-lg mb-2">Event Management Tools</h4>
      <ul className="list-disc pl-6 space-y-1">
        <li>
          Event Listing (`listEvents`)
          <ul className="list-circle pl-6 mt-1">
            <li>Comprehensive view of all scheduled and ongoing events</li>
            <li>Advanced filtering and sorting capabilities</li>
            <li>Real-time event status monitoring</li>
          </ul>
        </li>
      </ul>
    </div>

    <div>
      <h4 className="font-semibold text-lg mb-2">Recording Management</h4>
      <ul className="list-disc pl-6 space-y-2">
        <li>
          Schedule Management
          <ul className="list-circle pl-6 mt-1">
            <li>`scheduleRecordEvent`: Configure automated recording sessions</li>
            <li>`unscheduleRecordEvent`: Modify or cancel planned recordings</li>
            <li>Manage recording settings and storage options</li>
          </ul>
        </li>
      </ul>
    </div>
  </div>
</Card>

### Utility Features

<Card title="System Utilities" icon="wrench">
  <div className="space-y-4">
    <div className="border-b pb-4">
      <h4 className="font-semibold text-lg mb-2">System Health Monitoring</h4>
      <ul className="list-disc pl-6 space-y-1">
        <li>
          Connectivity Testing (`echo`)
          <ul className="list-circle pl-6 mt-1">
            <li>Real-time system connectivity verification</li>
            <li>Response time monitoring and latency checks</li>
            <li>Service availability testing</li>
          </ul>
        </li>
      </ul>
    </div>

    <div className="border-b pb-4">
      <h4 className="font-semibold text-lg mb-2">Security Features</h4>
      <ul className="list-disc pl-6 space-y-1">
        <li>Secure API key management and rotation</li>
        <li>Authentication and authorization handling</li>
        <li>Access control and permission management</li>
      </ul>
    </div>

    <div>
      <h4 className="font-semibold text-lg mb-2">Performance Monitoring</h4>
      <ul className="list-disc pl-6 space-y-1">
        <li>Resource utilization tracking and optimization</li>
        <li>System metrics and analytics</li>
        <li>Performance bottleneck identification</li>
      </ul>
    </div>
  </div>
</Card>

## Implementation Example 

Here's a complete example demonstrating how to implement and use the configured server in your application:

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp";
import handler from "./handler";

async function setupServer() {
  // Initialize the MCP server instance
  const server = new McpServer();
  
  // Retrieve API key from environment variables
  const apiKey = process.env.MEETING_BAAS_API_KEY;

  // Initialize the handler with server and API key
  await handler.initialize(server, apiKey);

  return server;
}

// Start the server with error handling
setupServer()
  .then((server) => {
    console.log("✅ MCP server successfully initialized with all capabilities");
  })
  .catch((error) => {
    console.error("❌ Failed to initialize MCP server:", error);
    process.exit(1);
  });
```


---

## Claude Integration

Configure and integrate Claude Desktop with Meeting BaaS MCP servers for AI-powered meeting assistance and automation

### Source: ./content/docs/mcp-servers/integrations/claude-integration.mdx


This guide explains how to integrate Claude Desktop with Meeting BaaS MCP server for enhanced meeting capabilities.

## Configuration Setup

### Step 1: Edit Configuration File

Open your Claude Desktop configuration file located at:

```bash
# For macOS
~/Library/Application Support/Claude/claude_desktop_config.json

# For Windows
%APPDATA%\Claude\claude_desktop_config.json

# For Linux
~/.config/Claude/claude_desktop_config.json
```

### Step 2: Add MCP Server Configuration

Add the following configuration to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "meetingbaas": {
      "command": "/bin/bash",
      "args": [
        "-c",
        "cd /path/to/meeting-mcp && (npm run build 1>&2) && MCP_FROM_CLAUDE=true node dist/index.js"
      ],
      "headers": {
        "x-api-key": "YOUR_API_KEY_FOR_MEETING_BAAS"
      },
      "botConfig": {
        "name": "Meeting Assistant",
        "image": "https://meetingbaas.com/static/972043b7d604bca0d4b0048c7dd67ad2/fc752/previewFeatures.avif",
        "entryMessage": "Hello, I'm a bot from Meeting Baas. I'll be taking notes for this meeting.",
        "deduplicationKey": "unique_key_to_override_restriction",
        "nooneJoinedTimeout": 600,
        "waitingRoomTimeout": 600,
        "speechToTextProvider": "Gladia",
        "speechToTextApiKey": "YOUR_SPEECH_TO_TEXT_API_KEY",
        "extra": {
          "meetingType": "sales",
          "summaryPrompt": "Focus on action items and decision points",
          "searchKeywords": ["budget", "timeline", "deliverables"],
          "timeStampHighlights": [
            {"time": "00:05:23", "note": "Discussion about Q2 sales numbers"},
            {"time": "00:12:47", "note": "Team disagreement on marketing strategy"}
          ],
          "participants": ["John Smith", "Jane Doe", "Bob Johnson"],
          "project": "Project Phoenix",
          "department": "Engineering",
          "priority": "High",
          "followupDate": "2023-12-15",
          "tags": ["technical", "planning", "retrospective"]
        },
        "calendarOAuth": {
          "platform": "Google",
          "clientId": "YOUR_OAUTH_CLIENT_ID",
          "clientSecret": "YOUR_OAUTH_CLIENT_SECRET",
          "refreshToken": "YOUR_REFRESH_TOKEN",
          "rawCalendarId": "primary@gmail.com"
        }
      }
    }
  }
}
```

## Configuration Parameters Explained

### Core Configuration

| Parameter | Description | Required |
|-----------|-------------|----------|
| `command` | Specifies the shell to execute the MCP server | Yes |
| `args` | Array of command-line arguments for server execution | Yes |
| `headers` | Authentication headers including API keys | Yes |

### Bot Configuration (`botConfig`)

| Parameter | Description | Required | Default |
|-----------|-------------|----------|---------|
| `name` | Display name of the bot in meetings | Yes | "Claude Assistant" |
| `image` | URL for bot's avatar image | No | System default |
| `entryMessage` | Bot's greeting message when joining meetings | No | - |
| `deduplicationKey` | Key to bypass 5-minute rejoin restriction | No | - |
| `nooneJoinedTimeout` | Timeout (seconds) if no participants join | No | 600 |
| `waitingRoomTimeout` | Timeout (seconds) for waiting room | No | 600 |

### Speech-to-Text Configuration

| Parameter | Description | Required |
|-----------|-------------|----------|
| `speechToTextProvider` | Provider for transcription service ("Gladia", "Runpod", "Default") | No |
| `speechToTextApiKey` | API key for the chosen provider | Required if provider specified |

### Calendar Integration (`calendarOAuth`)

| Parameter | Description | Required |
|-----------|-------------|----------|
| `platform` | Calendar platform ("Google" or "Microsoft") | Yes |
| `clientId` | OAuth client ID | Yes |
| `clientSecret` | OAuth client secret | Yes |
| `refreshToken` | OAuth refresh token | Yes |
| `rawCalendarId` | Specific calendar ID to integrate | No |

### Extended Metadata (`extra`)

The `extra` field allows for flexible metadata configuration to enhance AI capabilities:

```json
{
  "meetingType": "Type of meeting (sales, technical, etc.)",
  "summaryPrompt": "Custom prompt for meeting summaries",
  "searchKeywords": ["Array of keywords to track"],
  "timeStampHighlights": [
    {
      "time": "HH:MM:SS",
      "note": "Description of highlight"
    }
  ],
  "participants": ["Array of participant names"],
  "project": "Project identifier",
  "department": "Department name",
  "priority": "Meeting priority level",
  "followupDate": "YYYY-MM-DD",
  "tags": ["Array of relevant tags"]
}
```

## Security Considerations

1. **API Key Management**:
   - Use API keys associated with your corporate email account
   - Store API keys securely
   - Regularly rotate API keys
   - Never commit API keys to version control

2. **Access Control**:
   - Recordings and bot logs are automatically shared with same-domain colleagues
   - Implement proper access controls for sensitive meetings
   - Regular audit of access patterns

## QR Code API Integration

The QR Code API uses the same header name (`x-api-key`) but requires separate configuration. Configure it using one of these methods:

1. Environment Variable:
   ```bash
   export MEETING_BAAS_QR_API_KEY="your_api_key"
   ```

2. Direct Configuration:
   ```json
   {
     "qrCodeApi": {
       "apiKey": "YOUR_QR_API_KEY"
     }
   }
   ```

## Best Practices

1. **Meeting Setup**:
   - Configure appropriate timeouts
   - Use meaningful bot names
   - Set clear entry messages

2. **Data Management**:
   - Regular backup of configurations
   - Periodic review of stored meetings
   - Clean up unused recordings

3. **Integration Maintenance**:
   - Regular updates of dependencies
   - Monitor API usage
   - Keep OAuth tokens updated

## Troubleshooting

1. **Connection Issues**:
   - Verify API key validity
   - Check network connectivity
   - Ensure correct server path

2. **Bot Behavior**:
   - Verify timeout settings
   - Check log files for errors
   - Confirm OAuth credentials if using calendar integration

3. **Performance Issues**:
   - Monitor system resources
   - Check network bandwidth
   - Verify speech-to-text provider status

After configuration, restart Claude Desktop for changes to take effect.


---

## Cursor Integration

Configure and integrate Cursor IDE with Meeting BaaS MCP servers for AI-powered development assistance and code collaboration

### Source: ./content/docs/mcp-servers/integrations/cursor-integration.mdx


## Overview
This guide explains how to integrate the Meeting BaaS Model Context Protocol (MCP) server with Cursor, an AI-powered IDE. This integration enables enhanced AI capabilities within your development environment.

## Integration Steps

### Setting Up Cursor Integration

1. **Launch Cursor**
   - Open the Cursor IDE on your system
   - Ensure you're running the latest version for optimal compatibility

2. **Access Settings**
   - Click on the Settings icon (⚙️) in the bottom left corner
   - Alternatively, use the keyboard shortcut `Ctrl+,` (Windows/Linux) or `Cmd+,` (macOS)

3. **Configure Model Context Protocol**
   - Navigate to "Model Context Protocol" section
   - Click on "Add New Server"
   - Enter the following configuration:
     ```json
     {
       "name": "Meeting BaaS MCP",
       "type": "sse",
       "serverUrl": "http://localhost:7017/mcp"
     }
     ```
   - If authentication is required, add headers in the format:
     ```json
     {
       "Authorization": "Bearer your-token-here"
     }
     ```

## Development Guide

### Building the Project

```bash
# Build the project
npm run build
```
This command compiles the TypeScript code and generates the production-ready build.

### Testing

```bash
# Run MCP Inspector for testing
npm run inspect
```
The MCP Inspector provides a visual interface to test and debug your MCP server integration.

### Development Mode

```bash
# Start development server with auto-reload
npm run dev
```
Features:
- Hot reloading for code changes
- Real-time debugging information
- Automatic server restart on file changes

## Log Management

### Cleanup Command
```bash
npm run cleanup
```

The log management system includes sophisticated features:

- **Automated Log Cleanup**
  - Removes redundant log files
  - Cleans cached data periodically
  - Maintains optimal disk usage

- **Smart Log Filtering**
  - Filters out non-essential ping messages
  - Preserves critical error and warning logs
  - Implements log rotation for long-term management

- **Performance Optimization**
  - Reduces I/O operations
  - Minimizes memory footprint
  - Implements efficient log compression

## Project Architecture

```
project-root/
├── src/
│   ├── index.ts           # Main application entry point
│   ├── tools/             # MCP tool implementations
│   ├── resources/         # Resource definitions and handlers
│   ├── api/              # Meeting BaaS backend API client
│   ├── types/            # TypeScript type definitions
│   ├── config.ts         # Server configuration
│   └── utils/
│       ├── logging.ts    # Advanced logging system
│       └── tinyDb.ts     # Persistent state management
```

### Key Components

1. **Main Entry Point** (`src/index.ts`)
   - Server initialization
   - Route configuration
   - Middleware setup

2. **Tools Directory** (`src/tools/`)
   - Custom MCP tool implementations
   - Tool registration and management
   - Tool validation logic

3. **Resources** (`src/resources/`)
   - Static resource definitions
   - Resource loading and caching
   - Asset management

4. **API Client** (`src/api/`)
   - Meeting BaaS backend integration
   - API request handling
   - Response processing

5. **Type Definitions** (`src/types/`)
   - Interface definitions
   - Type guards
   - Shared type utilities

6. **Configuration** (`src/config.ts`)
   - Environment-based configuration
   - Server settings
   - Integration parameters

7. **Utilities** (`src/utils/`)
   - Logging system with advanced filtering
   - Database operations for bot state
   - Helper functions and common utilities

## Best Practices

1. **Error Handling**
   - Implement comprehensive error catching
   - Provide detailed error messages
   - Log errors with appropriate severity levels

2. **Security**
   - Use environment variables for sensitive data
   - Implement proper authentication
   - Regular security audits

3. **Performance**
   - Optimize resource usage
   - Implement caching where appropriate
   - Monitor server health metrics

## Troubleshooting

Common issues and solutions:

1. **Connection Issues**
   - Verify server URL is correct
   - Check if the server is running
   - Confirm network connectivity

2. **Authentication Errors**
   - Validate token format
   - Check token expiration
   - Verify header configuration

3. **Performance Problems**
   - Monitor log sizes
   - Check system resources
   - Review active connections


---

## Features

Core features and capabilities of Vercel MCP for Meeting BaaS

### Source: ./content/docs/mcp-servers/vercel-mcp/features.mdx


## Core Features

<Card title="Technical Architecture" icon="code">
  Built on top of the official Meeting BaaS TypeScript SDK (`@meeting-baas/sdk`), providing:

  - **Type Safety**: Complete TypeScript definitions for all API interactions
  - **Auto-Updates**: Synchronized with the latest OpenAPI specifications
  - **Cross-Platform Support**: Unified interface for Google Meet, Zoom, and Microsoft Teams
  - **Pre-built MCP Tools**: Ready-to-use AI system integrations
  - **Comprehensive API Access**: Type-safe functions for the entire Meeting BaaS API surface
</Card>

## Advanced Capabilities

### Meeting Management

<Cards>
  <Card title="Real-time Participation" icon="users">
    - Seamless meeting joining and leaving
    - Dynamic participant management
    - Real-time status updates
  </Card>

  <Card title="Recording & Transcription" icon="video">
    - Automated meeting recording
    - Real-time transcription services
    - Secure storage and retrieval
  </Card>

  <Card title="Bot Persona Management" icon="robot">
    - Customizable bot personalities
    - Context-aware responses
    - Dynamic behavior adaptation
  </Card>

  <Card title="Resource Optimization" icon="gauge">
    - Automatic resource cleanup
    - Performance monitoring
    - Usage optimization
  </Card>
</Cards>

### Calendar Integration

<Cards>
  <Card title="Multi-Platform Support" icon="calendar">
    - Google Calendar integration
    - Microsoft Outlook support
    - Other major calendar platforms
  </Card>

  <Card title="Smart Scheduling" icon="clock">
    - AI-powered scheduling automation
    - Conflict detection and resolution
    - Timezone-aware scheduling
  </Card>

  <Card title="Event Management" icon="calendar-check">
    - Real-time event updates
    - Attendee management
    - Resource allocation
  </Card>

  <Card title="Configuration" icon="gear">
    - Cross-platform sync
    - Custom scheduling rules
    - Integration preferences
  </Card>
</Cards>

### Bot Management

<Cards>
  <Card title="Monitoring & Analytics" icon="chart-line">
    - Real-time performance metrics
    - Usage statistics
    - Health monitoring
    - Automated alerts
  </Card>

  <Card title="Metadata Tracking" icon="database">
    - Detailed interaction logs
    - Performance analytics
    - Usage patterns
    - Error tracking
  </Card>

  <Card title="Dynamic Configuration" icon="sliders">
    - Real-time config updates
    - Feature toggles
    - Behavior customization
    - Environment-specific settings
  </Card>

  <Card title="Performance" icon="bolt">
    - Resource optimization
    - Load balancing
    - Caching strategies
    - Response time optimization
  </Card>
</Cards>


---

## Overview

Introduction to enterprise-grade Model Context Protocol server for Meeting BaaS

### Source: ./content/docs/mcp-servers/vercel-mcp/overview.mdx


MCP on Vercel is an enterprise-grade [Model Context Protocol](https://www.anthropic.com/news/model-context-protocol) server that powers the [chat.meetingbaas.com](https://chat.meetingbaas.com) platform. Built as an enhanced fork of the Vercel MCP template, it provides comprehensive Meeting BaaS integration for advanced meeting automation, AI-powered interactions, and intelligent meeting management.

<Card
  icon={<Github />}
  title="GitHub Repository"
  href="https://github.com/Meeting-Baas/mcp-on-vercel"
  description="Access the source code to deploy your own customized MCP server"
  external
/>

## Core Features

<Cards>
  <Card
    title="Meeting BaaS SDK Integration"
    icon={<Code className="text-blue-400" />}
  >
    Seamless integration with the TypeScript SDK providing type-safe access to all Meeting BaaS features including video management, bot control, and meeting automation.
  </Card>
  <Card
    title="Advanced Bot Management"
    icon={<MessageSquare className="text-green-400" />}
  >
    Comprehensive tools for deploying and managing AI-powered speaking agents with customizable personas, real-time transcription, and dynamic interaction capabilities.
  </Card>
  <Card
    title="Intelligent Calendar Integration"
    icon={<Calendar className="text-purple-400" />}
  >
    Smart calendar management with automated meeting scheduling, recording controls, and AI-assisted event organization across multiple platforms.
  </Card>
  <Card
    title="Enterprise-Ready Architecture"
    icon={<Cloud className="text-teal-400" />}
  >
    Built for scale with Vercel's serverless infrastructure, featuring Redis-backed session management, fluid compute optimization, and cross-platform compatibility.
  </Card>
</Cards>

## System Requirements

**Required Components:**
- Vercel account with deployment access
- Meeting BaaS API key
- Redis instance (for session state management)
- Node.js v16 or higher (for local development)

**Recommended Setup:**
- Vercel Pro/Enterprise account for extended compute capabilities
- Redis instance in the same region as your Vercel deployment
- Fluid Compute enabled for optimal performance

## Contributing

This project is maintained as an enhanced fork of the [original Vercel MCP template](https://github.com/vercel-labs/mcp-on-vercel). Contributions are welcome through pull requests and issues on our repository.

<Callout type="info">
  For production deployments, we recommend subscribing to our release notifications
  to stay updated with the latest security patches and feature enhancements.
</Callout>


---

## Tools

Technical details, deployment guides, and integration tools for Vercel MCP

### Source: ./content/docs/mcp-servers/vercel-mcp/tools.mdx


## Deployment Guide

<Tabs>
  <Tab title="One-Click Deploy" value="one-click">
    Deploy directly to Vercel with minimal configuration:
    
    <a href="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FMeeting-Baas%2Fmcp-on-vercel&env=REDIS_URL&envDescription=Redis%20URL%20for%20session%20management" target="_blank">
      <img src="https://vercel.com/button" alt="Deploy with Vercel" />
    </a>
    
    **Required Configuration:**
    1. Redis URL for session management
    2. Meeting BaaS API key (optional for development)
    3. Enable Fluid Compute in project settings
    4. Configure max duration in `vercel.json` (Pro/Enterprise)

  </Tab>
  <Tab title="Manual Deployment" value="manual">
    For customized deployment with full control:
    
    ```bash
    # Clone the repository
    git clone https://github.com/Meeting-Baas/mcp-on-vercel.git
    cd mcp-on-vercel
    
    # Install dependencies
    npm install
    
    # Configure environment
    cp .env.example .env
    # Edit .env with required credentials
    
    # Deploy
    npx vercel --prod
    ```

    **Post-Deployment Steps:**
    1. Configure environment variables in Vercel dashboard
    2. Enable Fluid Compute
    3. Adjust compute settings for your usage tier
    4. Set up monitoring and alerts

  </Tab>
</Tabs>

## Configuration

### Environment Variables

| Variable | Required | Description | Example |
|----------|----------|-------------|----------|
| `REDIS_URL` | Yes | Redis instance URL | `redis://user:pass@host:port` |
| `NODE_ENV` | No | Environment mode | `development` or `production` |
| `LOG_LEVEL` | No | Logging verbosity | `info`, `debug`, `error` |
| `BAAS_API_KEY` | Dev only | Meeting BaaS API key | `mbk_xxxx...` |

### Authentication Methods

The server supports multiple authentication approaches in order of precedence:

1. **HTTP Headers:**
   ```http
   x-meeting-baas-api-key: your-api-key
   x-meetingbaas-apikey: your-api-key
   x-api-key: your-api-key
   Authorization: Bearer your-api-key
   ```

2. **Request Body (POST):**
   ```json
   {
     "apiKey": "your-api-key"
   }
   ```

3. **Environment Variable (Development):**
   ```bash
   BAAS_API_KEY=your-api-key
   ```

<Callout type="warning">
  Production deployments should use header or body authentication methods. Environment
  variable authentication is restricted to development environments only.
</Callout>

## Integration Guide

### Claude Desktop Integration

Configure your MCP server in Claude Desktop:

1. Navigate to Settings > Model Context Protocol
2. Add new MCP Server with:
   ```yaml
   Name: Meeting BaaS MCP
   URL: [Your Vercel Deployment URL]
   Headers:
     x-api-key: [Your Meeting BaaS API Key]
   ```

### Custom Development

Extend the server's capabilities:

1. **Tool Customization:**
   - Modify `/lib/tools` for custom Meeting BaaS integrations
   - Add new tool definitions in `api/server.ts`

2. **Prompt Engineering:**
   - Update `/lib/prompts` for specialized use cases
   - Configure context handling in `api/server.ts`

3. **Testing:**
   ```bash
   # Start development server
   npm run dev

   # Run test client
   node scripts/test-client.mjs http://localhost:3000
   ```


---

## Overview

Introduction to Meeting MCP - A Standalone MCP Server for Meeting BaaS Integration

### Source: ./content/docs/mcp-servers/meeting-mcp/overview.mdx


Meeting MCP is a standalone [Model Context Protocol](https://www.anthropic.com/news/model-context-protocol) server that provides AI assistants with access to Meeting BaaS data and capabilities. The server can be deployed locally or on your own infrastructure, making it ideal for use with Claude Desktop and other MCP clients.

<Card
  icon={<Github />}
  title="GitHub Repository"
  href="https://github.com/Meeting-Baas/meeting-mcp"
  description="Clone the repository to deploy your own MCP server"
  external
/>

## Features

<Cards>
  <Card title="Meeting Management" icon={<Video className="text-blue-400" />}>
    Create, join, and manage meeting bots with automatic recording and
    transcription.
  </Card>
  <Card title="Transcript Search" icon={<Search className="text-green-400" />}>
    Search and analyze meeting transcripts for specific content or speakers.
  </Card>
  <Card
    title="Calendar Integration"
    icon={<Calendar className="text-purple-400" />}
  >
    Connect calendars and automatically schedule meeting recordings.
  </Card>
  <Card title="QR Code Generation" icon={<QrCode className="text-amber-400" />}>
    Generate AI-powered QR code images that can be used as bot avatars.
  </Card>
  <Card
    title="Key Moment Identification"
    icon={<Clock className="text-teal-400" />}
  >
    Automatically identify and share important moments from meetings.
  </Card>
  <Card title="Link Sharing" icon={<Link className="text-indigo-400" />}>
    Generate shareable links to meetings and specific timestamps.
  </Card>
  <Card title="Local Deployment" icon={<HardDrive className="text-red-400" />}>
    Run completely on your own infrastructure for enhanced security and control.
  </Card>
</Cards>

## Prerequisites

Before getting started, you'll need:

- Node.js (v16 or later)
- npm
- A [Meeting BaaS API key](https://meetingbaas.com)
- (Optional) A QR Code AI API key for QR code generation

## Installation

<Steps>
  <Step title="Clone the Repository">
    ```bash
    git clone https://github.com/Meeting-Baas/meeting-mcp.git
    cd meeting-mcp
    ```
  </Step>
  <Step title="Install Dependencies">
    ```bash
    npm install
    ```
  </Step>
  <Step title="Build the Project">
    ```bash
    npm run build
    ```
  </Step>
  <Step title="Start the Server">
    ```bash
    npm run start
    ```
    By default, the server runs on port 7017 and exposes the MCP endpoint at http://localhost:7017/mcp.
  </Step>
</Steps>

## Authentication

The server expects an API key in the `x-api-key` header for authentication. You can configure the default API key in the configuration file.

Many tools also support direct authentication through parameters (named with "WithCredentials"), allowing you to provide the API key directly in the query rather than through headers.

## Integration with Claude Desktop

<Steps>
  <Step title="Edit the Claude Desktop Configuration File">
    ```bash
    # On Mac/Linux
    vim ~/Library/Application\ Support/Claude/claude_desktop_config.json
    
    # On Windows
    notepad %APPDATA%\Claude\claude_desktop_config.json
    ```
  </Step>
  
  <Step title="Add the Meeting BaaS MCP Server Configuration">
    ```json
    {
      "mcpServers": {
        "meetingbaas": {
          "command": "/bin/bash",
          "args": [
            "-c",
            "cd /path/to/meeting-mcp && (npm run build 1>&2) && MCP_FROM_CLAUDE=true node dist/index.js"
          ],
          "headers": {
            "x-api-key": "YOUR_API_KEY_FOR_MEETING_BAAS"
          },
          "botConfig": {
            "name": "Meeting Assistant",
            "image": "https://meetingbaas.com/static/972043b7d604bca0d4b0048c7dd67ad2/fc752/previewFeatures.avif",
            "entryMessage": "Hello, I'm a bot from Meeting Baas. I'll be taking notes for this meeting.",
            "deduplicationKey": "unique_key_to_override_restriction",
            "nooneJoinedTimeout": 600,
            "waitingRoomTimeout": 600,
            "speechToTextProvider": "Gladia",
            "speechToTextApiKey": "YOUR_SPEECH_TO_TEXT_API_KEY",
            "extra": {
              "meetingType": "sales",
              "summaryPrompt": "Focus on action items and decision points",
              "searchKeywords": ["budget", "timeline", "deliverables"]
            }
          }
        }
      }
    }
    ```
    
    <Callout>
      Replace `/path/to/meeting-mcp` with the actual path to your local repository and `YOUR_API_KEY` with your actual Meeting BaaS API key.
    </Callout>
  </Step>
  
  <Step title="Restart Claude Desktop">
    Close and reopen Claude Desktop to apply the changes.
  </Step>
</Steps>

## Development

For development purposes, you can:

```bash
# Run in development mode with auto-reload
npm run dev

# Test with MCP Inspector
npm run inspect

# Clean up logs
npm run cleanup
```

## Project Structure

- `src/index.ts`: Main entry point
- `src/tools/`: Tool implementations
- `src/resources/`: Resource definitions
- `src/api/`: API client for the Meeting BaaS backend
- `src/types/`: TypeScript type definitions
- `src/config.ts`: Server configuration
- `src/utils/`: Utility functions


---

## Architecture

Learn more about the architecture of Transcript Seeker.

### Source: ./content/docs/transcript-seeker/concepts/architecture.mdx


Understanding the architecture of Transcript Seeker is crucial for both setting up and deploying Transcript Seeker. Below is a diagram illustrating the core components:

<ImageZoom
  src={'/assets/architecture.svg'}
  width={1024}
  height={1024}
  className="dark:invert"
  rmiz={{
    classDialog: 'dark:[&_img]:invert',
  }}
/>


---

## Environment Variables

Configuring Environment Variables for Transcript Seeker.

### Source: ./content/docs/transcript-seeker/concepts/environment-variables.mdx


Let's learn how to configure environment variables for Transcript Seeker.
Transcript Seeker uses `dotenv-cli` to load the environment variables, making it easy for development.
Transcript Seeker follows this structure for different environments:

- `.env.development.local` for development
- `.env.production.local` for production

You can load a specific environment file by running the following command:

```bash title="Terminal"
export NODE_ENV="development"
```

Now, let's configure the environment variables.

## Client

Create a `.env.development.local` file in the below directory of your project and add the following environment variables:

<Files>
  <Folder name="apps">
    <Folder name="api" defaultOpen>
        <File name="..." />
      <File name=".env.development.local" />
    </Folder>
    <Folder name="proxy" defaultOpen>
        <File name="..." />
      <File name=".env.development.local" />
    </Folder>
    <Folder name="web">
      <File name="..." />
    </Folder>
  </Folder>
  <Folder name="packages">
    <Folder name="db" defaultOpen>
          <File name="..." />

    </Folder>
    <Folder name="shared" defaultOpen>
          <File name="..." />
    </Folder>
    <Folder name="ui" defaultOpen>
      <File name="..." />
    </Folder>

  </Folder>
  <Folder name="tooling">
    <Folder name="eslint" defaultOpen>
          <File name="..." />

    </Folder>
    <Folder name="github" defaultOpen>
          <File name="..." />
    </Folder>
    <Folder name="prettier" defaultOpen>
      <File name="..." />
    </Folder>
        <Folder name="tailwind" defaultOpen>
      <File name="..." />
    </Folder>
        <Folder name="typescript" defaultOpen>
      <File name="..." />
    </Folder>

  </Folder>
  <File name=".env.development.local" className="bg-fd-accent" />
  <File name="package.json" />
</Files>

<Steps>
<Step>
### Vite Port Configuration

These values are used by vite to configure the url the server listens on.

```txt title=".env.development.local"
VITE_CLIENT_PORT=5173
VITE_CLIENT_HOST=0.0.0.0
```

</Step>

<Step>
### Proxy Configuration

This Proxy URL is used to forward requests from the client to the respective server, helping to avoid client-side CORS errors. An example proxy server is provided in the repository.

```txt title=".env.development.local"
VITE_PROXY_URL=http://localhost:3000
```

</Step>

<Step>
### API Configuration

This API URL is used by the calendars functionality of Transcript Seeker. It allows the app to perform authentication and retrieve the user's calendar data.

```txt title=".env.development.local"
VITE_API_URL=http://localhost:3001
```

</Step>

<Step>
### S3 Configuration

This environment variable is used to indicate to the client where the video recordings are stored.

```txt title=".env.development.local"
VITE_S3_PREFIX=https://s3.eu-west-3.amazonaws.com/meeting-baas-video
```

</Step>
</Steps>

## Proxy

Create a `.env.development.local` file in the below directory of your project and add the following environment variables:

<Files>
  <Folder name="apps" defaultOpen>
    <Folder name="api" >
        <File name="..." />
      <File name=".env.development.local" />
    </Folder>
    <Folder name="proxy" defaultOpen>
        <File name="..." />
      <File name=".env.development.local"   className="bg-fd-accent"  />
    </Folder>
    <Folder name="web">
      <File name="..." />
    </Folder>
  </Folder>
  <Folder name="packages">
    <Folder name="db" defaultOpen>
          <File name="..." />

    </Folder>
    <Folder name="shared" defaultOpen>
          <File name="..." />
    </Folder>
    <Folder name="ui" defaultOpen>
      <File name="..." />
    </Folder>

  </Folder>
  <Folder name="tooling">
    <Folder name="eslint" defaultOpen>
          <File name="..." />

    </Folder>
    <Folder name="github" defaultOpen>
          <File name="..." />
    </Folder>
    <Folder name="prettier" defaultOpen>
      <File name="..." />
    </Folder>
        <Folder name="tailwind" defaultOpen>
      <File name="..." />
    </Folder>
        <Folder name="typescript" defaultOpen>
      <File name="..." />
    </Folder>

  </Folder>
  <File name=".env.development.local" />
  <File name="package.json" />
</Files>

<Steps>
<Step>
### MeetingBaas Proxy Configuration

These values are used by the api to figure out the api url for baas servers.

```txt title=".env.development.local"
MEETINGBAAS_API_URL="https://api.meetingbaas.com"
MEETINGBAAS_S3_URL="https://s3.eu-west-3.amazonaws.com/meeting-baas-video"
```

</Step>
</Steps>

## API

Create a `.env.development.local` file in the below directory of your project and add the following environment variables:

<Files>
  <Folder name="apps" defaultOpen>
    <Folder name="api" defaultOpen>
        <File name="..." />
      <File name=".env.development.local"  className="bg-fd-accent" />
    </Folder>
    <Folder name="proxy">
        <File name="..." />
      <File name=".env.development.local" />
    </Folder>
    <Folder name="web">
      <File name="..." />
    </Folder>
  </Folder>
  <Folder name="packages">
    <Folder name="db" defaultOpen>
          <File name="..." />

    </Folder>
    <Folder name="shared" defaultOpen>
          <File name="..." />
    </Folder>
    <Folder name="ui" defaultOpen>
      <File name="..." />
    </Folder>

  </Folder>
  <Folder name="tooling">
    <Folder name="eslint" defaultOpen>
          <File name="..." />

    </Folder>
    <Folder name="github" defaultOpen>
          <File name="..." />
    </Folder>
    <Folder name="prettier" defaultOpen>
      <File name="..." />
    </Folder>
        <Folder name="tailwind" defaultOpen>
      <File name="..." />
    </Folder>
        <Folder name="typescript" defaultOpen>
      <File name="..." />
    </Folder>

  </Folder>
  <File name=".env.development.local" />
  <File name="package.json" />
</Files>

<Steps>
<Step>
### MeetingBaas Configuration

These values are used by the api to figure out the api url for baas servers.

```txt title=".env.development.local"
NITRO_MEETINGBAAS_API_URL="https://api.meetingbaas.com"
NITRO_MEETINGBAAS_S3_URL="https://s3.eu-west-3.amazonaws.com/meeting-baas-video"
NITRO_TRUSTED_ORIGINS="http://localhost:5173" # comma separated list of trusted origins
```

</Step>

<Step>
### Google Authentication Configuration

These values are used by the api to perform google authentication. Please follow the [guide](/docs/transcript-seeker/concepts/api/authentication) for more details:

```txt title=".env.development.local"
GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET=""
```

</Step>

<Step>
### Turso Database Configuration

These values are used by the api to store user autehtncation data. Please follow the [guide](/docs/transcript-seeker/concepts/api/database) for more details:

```txt title=".env.development.local"
TURSO_DATABASE_URL=""
TURSO_AUTH_TOKEN=""
```

</Step>

<Step>
### Authentication Configuration

<Callout>
  When deploying to Google Cloud Run with a custom domain, you should set the
  `BETTER_AUTH_URL` environment variable to the custom domain.
</Callout>

These values are used by the api to perform authentication. Please follow the [guide](/docs/transcript-seeker/concepts/api/authentication) for more details:

```txt title=".env.development.local"
BETTER_AUTH_SECRET=""
BETTER_AUTH_URL="http://localhost:3001"
API_TRUSTED_ORIGINS="http://localhost:5173"
```

<Callout>
  The `API_TRUSTED_ORIGINS` is not only used for authentication but also for
  CORS configuration.
</Callout>

</Step>

</Steps>


---

## Installation

Learn how to configure Transcript Seeker.

### Source: ./content/docs/transcript-seeker/getting-started/installation.mdx


<Steps>
<Step>
### Clone the Repo

Create a new app with `create-turbo`, it requires Node.js 20+.

<Tabs groupId='package-manager' persist items={['npm', 'pnpm', 'yarn']}>

```bash tab="npm"
npx create-turbo@latest -e https://github.com/Meeting-Baas/transcript-seeker
```

```bash tab="pnpm"
pnpm dlx create-turbo@latest -e https://github.com/Meeting-Baas/transcript-seeker
```

```bash tab="yarn"
yarn dlx create-turbo@latest -e https://github.com/Meeting-Baas/transcript-seeker
```

</Tabs>

It will ask you the following questions:

- Which package manager would you like to use? PNPM

<Callout type="warn">
  Use pnpm as the package manager or the installation will fail.
</Callout>

</Step>

<Step>
### Configure Environment Variables

Copy the `.env.example` file to a `.env.development.local` file in the following folders within your project structure and add the necessary environment variables:

To learn more about configuring the environment variables, follow this [guide](/docs/transcript-seeker/concepts/environment-variables).

<Files>
  <Folder name="apps" defaultOpen>
    <Folder name="api" defaultOpen>
      <File name=".env.development.local" />
    </Folder>
  </Folder>
  <Folder name="apps" defaultOpen>
    <Folder name="proxy" defaultOpen>
      <File name=".env.development.local" />
    </Folder>
  </Folder>
  <File name=".env.development.local" />
  <File name="package.json" />
</Files>

After setting up the environment files, execute the following command to set the environment to development mode:

```bash title="Terminal"
export NODE_ENV="development"
```

</Step>

<Step>
### Run the App

Now, start the development server:

<Tabs groupId='package-manager' persist items={['pnpm', 'npm', 'yarn']}>

```bash tab="pnpm"
pnpm turbo run dev
```

```bash tab="npm"
npm run dev
```

```bash tab="yarn"
yarn run dev
```

</Tabs>

<Callout border={false} type="warning">

If `pnpm run dev` doesn't work, you can try the following:

<Tabs groupId='package-manager' persist items={['pnpm', 'npm', 'yarn']}>

```bash tab="pnpm"
pnpm add turbo --global
turbo dev
```

```bash tab="npm"
npm install turbo --global
turbo dev
```

```bash tab="yarn"
yarn install turbo --global
turbo dev
```

</Tabs>
</Callout>
</Step>
</Steps>


---

## Contributing

Contributing to Transcript Seeker

### Source: ./content/docs/transcript-seeker/guides/contributing.mdx


Hey there! 👋 Welcome to the **Transcript-Seeker** project! We're absolutely thrilled that you're interested in contributing. Whether you're fixing a bug, adding a feature, or sharing an idea, your efforts help make our project better and more useful for everyone. Here are some easy-to-follow guidelines to help you dive in!

## Table of Contents

- [Issues](#issues)
- [Pull Requests](#pull-requests)
- [Local Setup](#local-setup)
- [Environment Variables](#environment-variables)
- [Conventional Commits](#conventional-commits)
- [Code Formatting](#code-formatting)

## Issues

Have you found a bug, got a suggestion, or stumbled upon something that doesn't work as expected? No problem! Feel free to [open an issue](https://github.com/Meeting-Baas/transcript-seeker/issues). When submitting an issue, try to include a clear and concise title and as many details as possible. The more info you provide, the faster we can jump in and help!

## Pull Requests

We love pull requests! 🎉 If you'd like to make a contribution, whether it’s a bug fix, a feature, or a small improvement, follow these steps:

1. **Fork the repo** to your GitHub account.
2. **Create a new branch** with a meaningful name:
   - For bug fixes: `fix/issue-123-bug-description`
   - For new features: `feat/awesome-new-feature`
3. Make your changes, and make sure you follow our [Conventional Commits](#conventional-commits) guidelines.
4. **Push your branch** to your forked repository.
5. Open a **pull request** from your branch to the `main` branch of this repo.
6. Before submitting, make sure your code is clean by running:
   ```bash
   pnpm typecheck
   ```
   This will help catch any issues early on. 🚀

## Local Setup

Ready to jump into the code? Awesome! Please follow this [guide](/docs/transcript-seeker/getting-started/installation) to get started.

## Environment Variables

To learn more about configuring the environment variables, follow this [guide](/docs/transcript-seeker/concepts/environment-variables).

## Conventional Commits

We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) to keep our commit history clean and organized. It makes it easier for everyone to understand what's been done at a glance. Commit messages should use the following format:

```
<type>(<scope>): <description>
```

For example:

- `feat(homepage): redesign the layout`
- `fix(styles): correct position of server status`

Some common commit types:

- `feat`: A new feature.
- `fix`: A bug fix.
- `docs`: Documentation changes.
- `style`: Code style changes (formatting, missing semicolons, etc.).

## Code Formatting

To keep the codebase consistent and readable, we recommend running a few checks before opening a pull request:

1. **Lint fixes**:

   ```bash
   pnpm lint:fix
   ```

2. **Run type checks** to ensure there are no type issues:
   ```bash
   pnpm typecheck
   ```

Your code should be well-tested, clear, and follow our best practices. Remember, every contribution makes a difference, and we deeply appreciate your help in making **Transcript-Seeker** better! 🎉

Thanks a ton for contributing, and welcome aboard! If you need any help, don’t hesitate to ask. Let’s make something amazing together. 🚀


---

## Turso

Learn how to create a turso database.

### Source: ./content/docs/transcript-seeker/guides/turso.mdx


    <div className="relative w-full h-none" style={{  paddingBottom: 'calc(55.443786982248525% + 41px)' }}>
      <iframe
        src="https://demo.arcade.software/qS6sJh2Y45to6tEogy8H?embed&embed_mobile=tab&embed_desktop=inline&show_copy_link=true"
        title="Creating A Turso Database"
        frameBorder="0"
        loading="lazy"
        allowFullScreen
        allow="clipboard-write"
        style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', colorScheme: 'light' }}
      />
    </div>

After creating the database, please push the schema onto the db:

```bash
cd apps/api
pnpm db:push
```


---

## Environment Variables

Configure environment variables for Speaking Bots

### Source: ./content/docs/speaking-bots/getting-started/environment-variables.mdx


Speaking Bots requires several API keys and configuration values to function properly. Here's a quick setup guide:

1. Copy the example environment file:

```bash
cp .env.example .env
```

{" "}

<Callout type="warn">
  Never commit your `.env` file to version control. It contains sensitive API
  keys that should be kept private.
</Callout>

2. Add your credentials to `.env`: You'll need 4 required API keys for core functionality (MeetingBaas, OpenAI, Speech-to-Text, and Text-to-Speech). Additional keys can be added later for optional features.

## Core Bot Functionality (Required)

<Steps>
<Step>
### MeetingBaas Configuration

Required for sending meeting bots as personas to various platforms:

```txt
MEETING_BAAS_API_KEY=your_meetingbaas_api_key_here
```

Get your API key by:

1. Signing up for [MeetingBaas](https://meetingbaas.com)
2. Accessing your API key from the MeetingBaas dashboard

</Step>

<Step>
### OpenAI Configuration

Powers in-meeting AI interactions and persona management:

```txt
OPENAI_API_KEY=your_openai_api_key_here
```

<Callout>
  This key is used both for in-meeting interactions and persona creation functionality.
</Callout>
</Step>

<Step>
### Speech-to-Text Configuration

Choose one of the following options:

#### Option 1: Deepgram

```txt
DEEPGRAM_API_KEY=your_deepgram_api_key_here
```

#### Option 2: Gladia

```txt
GLADIA_API_KEY=your_gladia_api_key_here
```

</Step>

<Step>
### Text-to-Speech Configuration

Required for voice synthesis and persona voices:

```txt
CARTESIA_API_KEY=your_cartesia_api_key_here
CARTESIA_VOICE_ID="79a125e8-cd45-4c13-8a67-188112f4dd22"
```

</Step>
</Steps>

## Optional Features

### Multiple Bots Support

<Steps>
<Step>
Required for running multiple bots in local development:
```txt
NGROK_AUTHTOKEN=your_ngrok_auth_token_here
```

<Callout>
  Follow our [Ngrok Setup Guide](/docs/speaking-bots/getting-started/ngrok-setup) to get your auth token.
</Callout>
</Step>
</Steps>

### Persona Creation

<Steps>
  <Step>Required for AI image generation and storage:</Step>
</Steps>


---

## Ngrok setup

We create ngrok tunnel(s) for running several bots at once on your local machine

### Source: ./content/docs/speaking-bots/getting-started/ngrok-setup.mdx


## Local Setup

For running one or more bots locally, you'll need an ngrok authtoken. Follow these steps:

1. Sign up for a free account at [ngrok.com](https://dashboard.ngrok.com/signup)
2. After signing up, get your authtoken from the [Your Authtoken page](https://dashboard.ngrok.com/get-started/your-authtoken)
3. Add the token to your `.env` file or set it as an environment variable:

```bash
NGROK_AUTHTOKEN=your_ngrok_auth_token_here
```

That's it folks :)

<Accordions>
  <Accordion title="Configuration modification">

We provide a ready-to-use configuration file in the repository at `config/ngrok/config.yml`. You can either use this file directly or create your own configuration.

The default location for the ngrok configuration file varies by operating system:

- Linux: `~/.config/ngrok/ngrok.yml`
- macOS: `~/Library/Application Support/ngrok/ngrok.yml`
- Windows: `%HOMEPATH%\AppData\Local\ngrok\ngrok.yml`

To verify your configuration file location, you can run:

```bash
ngrok config check
```

If you want to create or edit your own configuration file, here's what it should contain:

```yaml
version: '3'
agent:
  authtoken: YOUR_AUTH_TOKEN

tunnels:
  proxy1:
    proto: http
    addr: 8766
  proxy2:
    proto: http
    addr: 8768
```

For more detailed information about ngrok configuration options, see the [official ngrok configuration documentation](https://ngrok.com/docs/agent/config/).

## Usage

This sets up two separate tunnels (proxy1 and proxy2) that will be used by your bots to establish WebSocket connections with the meeting platforms. To start both tunnels simultaneously, run:

```bash
ngrok start --all --config config/ngrok/config.yml
```

The free tier of ngrok limits you to 2 concurrent tunnels, which means you can run up to 2 bots simultaneously in local development mode.

  </Accordion>
</Accordions>

## WebSocket URL Resolution

When running the server in local development mode, it will automatically detect and use your ngrok URLs. The server determines the WebSocket URL to use in the following priority order:

1. User-provided URL in the request (if specified in the `websocket_url` field)
2. `BASE_URL` environment variable (recommended for production)
3. ngrok URL in local development mode
4. Auto-detection from request headers (fallback, not reliable in production)

To use local development mode with automatic ngrok detection:

```bash
# Start the API server with local development mode enabled
poetry run python run.py --local-dev
```

## Troubleshooting WebSocket Connections

### Common Issues

1. **Timing Issues with ngrok and Meeting Baas Bots**

   Sometimes, due to WebSocket connection delays through ngrok, the Meeting Baas bots may join the meeting before your local bot connects. If this happens:

   - Simply press `Enter` to respawn your bot
   - This will reinitiate the connection and allow your bot to join the meeting

2. **Connection failures**
   - Make sure ngrok is running with the correct configuration
   - Verify that you've entered the correct ngrok URLs when prompted
   - Check that your ngrok URLs are accessible (try opening in a browser)
   - Make sure you're using the `wss://` protocol with ngrok URLs

### Production Considerations

For production deployments, you should:

1. Set the `BASE_URL` environment variable to your server's public domain:
   ```
   export BASE_URL=https://your-server-domain.com
   ```
2. Ensure your server is accessible on the public internet
3. Consider using HTTPS/WSS for secure connections in production


---

## Set Up

Set up your development environment for Speaking Bots

### Source: ./content/docs/speaking-bots/getting-started/set-up.mdx


## Installation

### 0. Clone Repository

If you haven't already, clone the repository and navigate to it:

```bash
git clone https://github.com/Meeting-Baas/speaking-meeting-bot.git
cd speaking-meeting-bot
```

### 1. Prerequisites

- Python 3.11+
- `grpc_tools` for protocol buffer compilation
- Ngrok for local development (follow our [Ngrok Setup Guide](/docs/speaking-bots/getting-started/ngrok-setup))
- Poetry for dependency management

You'll also need system dependencies for scientific libraries:

```bash
# macOS (using Homebrew)
brew install llvm cython

# Ubuntu/Debian
sudo apt-get install llvm python3-dev cython

# Fedora/RHEL
sudo dnf install llvm-devel python3-devel Cython
```

### 2. Set Up Poetry Environment

```bash
# Install Poetry (Unix/macOS)
curl -sSL https://install.python-poetry.org | python3 -

# Install Poetry (Windows)
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py -

# Configure Poetry to use Python 3.11+
poetry env use python3.11

# Install dependencies with LLVM config path
# On macOS:
LLVM_CONFIG=$(brew --prefix llvm)/bin/llvm-config poetry install
# On Linux (path may vary):
# LLVM_CONFIG=/usr/bin/llvm-config poetry install

# Activate virtual environment
poetry shell
```

### 3. Compile Protocol Buffers

```bash
poetry run python -m grpc_tools.protoc --proto_path=./protobufs --python_out=./protobufs frames.proto
```

Protocol Buffers are used here by Pipecat to define a structured message format for real-time communication between components of the Speaking Bots system. Specifically, the [`frames.proto`](https://github.com/pipecat-ai/pipecat/blob/635aa6eb5bdee382729613b58279befdc5bc8eaf/src/pipecat/frames/frames.proto#L9) file defines three main message types:

1. `TextFrame`: For handling text-based messages
2. `AudioRawFrame`: For managing raw audio data with properties like sample rate and channels
3. `TranscriptionFrame`: For handling speech-to-text transcription results

Protocol Buffers is the backbone of consistent data serialization across services.
Read more in the [official Protocol Buffer documentation](https://protobuf.dev/downloads/) and [this Python Protocol Buffers tutorial](https://www.blog.pythonlibrary.org/2023/08/30/an-intro-to-protocol-buffers-with-python/).

### 4. Configure Environment

Create a `.env` file based on the template:

```bash
cp env.example .env
```

Edit the `.env` file with your API keys. You'll need:

**Required API Keys:**

- `MEETING_BAAS_API_KEY`: For meeting platform integration
- `OPENAI_API_KEY`: For the conversation LLM
- `CARTESIA_API_KEY`: For text-to-speech
- `GLADIA_API_KEY` or `DEEPGRAM_API_KEY`: For speech-to-text

For production, also set:

```
BASE_URL=https://your-server-domain.com
```

See our full [Environment Variables Guide](/docs/speaking-bots/getting-started/environment-variables) for more details.

### 5. Run the API Server

The project now follows an API-first approach. There are two ways to run the server:

```bash
# Standard mode
poetry run uvicorn app:app --reload --host 0.0.0.0 --port 8766

# Local development mode with ngrok auto-configuration
poetry run python run.py --local-dev
```

Once the server is running, you can access:

- Interactive API docs: `http://localhost:8766/docs`
- OpenAPI specification: `http://localhost:8766/openapi.json`

To create a bot via the API:

```bash
curl -X POST http://localhost:8766/run-bots \
  -H "Content-Type: application/json" \
  -d '{
    "meeting_url": "https://meet.google.com/xxx-yyyy-zzz",
    "personas": ["interviewer"],
    "meeting_baas_api_key": "your-api-key"
  }'
```

You can also use our CLI tools for testing:

```bash
poetry run python scripts/batch.py -c 1 --meeting-url LINK
```

Follow our [Command Line Guide](/docs/speaking-bots/command-line) for more examples and options.


---

## Google Meet Authentication

Send authenticated Google Meet bots that sign in as Google Workspace users via SAML SSO, bypass the waiting room, and join sign-in-restricted meetings

### Source: ./content/docs/api-v2/authenticated-bots/meet/index.mdx


# Authenticated Google Meet Bots

By default, Meeting BaaS bots join Google Meet as anonymous guests. That works for open meetings, but it falls short when a meeting is **restricted to signed-in users**, restricted to a host's organization, or configured to send guests to a waiting room.

Authenticated Meet bots solve this. Each bot signs in as a real **Google Workspace user** from a domain you control, using **SAML SSO**, before it joins the call. To Meet, the bot looks like any other signed-in participant.

<Callout type="info">
This feature is Google Meet–only. For Zoom authentication, see [Zoom Integration](/docs/api-v2/authenticated-bots/zoom). For Microsoft Teams, see [Microsoft Teams Authentication](/docs/api-v2/authenticated-bots/teams). Leave `meet_config` `null` for anonymous Meet joins.
</Callout>

## Why authenticate

| Scenario | Anonymous bot | Authenticated bot |
|----------|---------------|-------------------|
| Open meeting, anyone with link | ✅ Joins | ✅ Joins |
| "Only people in the organization can join" | ❌ `MEET_LOGIN_REQUIRED` | ✅ Joins as a Workspace user |
| Guests sent to a waiting room | ⏳ Waits for admit | ✅ Bypasses via verified queue (with `email_group`) |
| Calendar-invited participants auto-admitted | ❌ Not invited | ✅ Invite the Google Group, land in the verified queue |

## How it works

Authentication is built on three resources. You configure the first two once, then reference them per bot.

<Steps>
<Step>
**Meet Workspace** — the parent resource representing one Google Workspace's SAML SSO configuration. It holds the SAML signing certificate and private key shared by every login under it. You create one per Google Workspace domain. See [Setup](/docs/api-v2/authenticated-bots/meet/setup).
</Step>
<Step>
**Meet Logins** — one per Google Workspace user the bots sign in as. Many logins can share a single workspace. Logins can be grouped into round-robin pools by `email_group`.
</Step>
<Step>
**`meet_config` on the bot** — when you create a bot, you tell it which login (or pool) to use. The dispatcher signs the bot in via SAML SSO using the workspace's keypair, then joins the meeting.
</Step>
</Steps>

```text
Meet Workspace (domain + SAML cert/key)
        │
        ├── Meet Login  bot1@bots.acme.com   ┐
        ├── Meet Login  bot2@bots.acme.com   ├─ email_group: bots@bots.acme.com (round-robin pool)
        └── Meet Login  bot3@bots.acme.com   ┘
                                │
        POST /v2/bots  { meet_config: { email_group: "bots@bots.acme.com" } }
                                │
                 least-loaded active login is assigned → bot signs in → joins Meet
```

## The `meet_config` object

All Meet authentication options are passed in a single `meet_config` object on `POST /v2/bots`:

```json
{
  "bot_name": "Recording Bot",
  "meeting_url": "https://meet.google.com/abc-defg-hij",
  "meet_config": {
    "email_group": "bots@bots.acme.com",
    "fallback": "fail"
  }
}
```

| Parameter | Description |
|-----------|-------------|
| `email_group` | Round-robin pool selector. The bot is assigned the **least-loaded active login** in this pool. Preferred for most use cases — **takes priority over `credential_id`**. Pass `""` to round-robin across all of the team's active logins. |
| `credential_id` | Pin one specific login (UUID) for this bot. |
| `fallback` | What to do when no login slot is available: `fail` (default) fails bot creation with `MEET_LOGIN_UNAVAILABLE`; `anonymous` silently falls back to an anonymous join. |

Leave `meet_config` `null` for anonymous Meet joins, Zoom, or Microsoft Teams.

## Key concepts

### The verified queue and the waiting room

When you put a login's `email_group` (a Google Group address) on the **calendar invite** for a meeting, the assigned bot lands in Meet's **verified queue** and bypasses the waiting room — it is treated as an invited participant. This is the recommended pattern for unattended recording. Without an invite, an authenticated bot still benefits from being a signed-in organizational user, but may still hit the host's admission rules.

### Round-robin pools and concurrency

Each login supports up to **20 concurrent SSO sessions**. When you dispatch bots with an `email_group`, the assigner picks the least-loaded active login and skips any login at capacity. If every login in a pool is saturated, bot creation fails with `MEET_LOGIN_UNAVAILABLE` (or falls back to anonymous, per your `fallback` setting). Create more logins to raise the ceiling, and configure a [**Meet Login Utilization** alert](/docs/api-v2/alerts#meet-login-alerts) to stay ahead of saturation (the [utilization endpoint](/docs/api-v2/authenticated-bots/meet/sending-authenticated-bots#monitoring-pool-utilization) gives an on-demand view).

### States

Both workspaces and logins track health with a `state` field:

- **`active`** — healthy and usable.
- **`invalid`** — the system auto-disabled the resource after a failure (a SAML rejection for workspaces; a bot login failure such as a suspended user, or a bot account that never completed its first-time interactive "Welcome to Workspace" login). Re-enable manually via `PATCH` after fixing the underlying issue.

When a resource flips to `invalid`, `last_error_message` and `last_error_at` explain why.

### Security

The SAML certificate and private key are encrypted at rest using **AES-256-GCM**. The `private_key_pem` is **never returned** in any API response — not on create, and not on subsequent reads. If you lose it, rotate the keypair via `PATCH /v2/meet-workspaces/{workspace_id}`.

## Pages in this section

<Cards>
  <Card title="Setup" href="/docs/api-v2/authenticated-bots/meet/setup">
    Create a meet workspace, configure the Legacy SSO profile, create the bot accounts and complete their welcome flow, then assign the profile and add logins.
  </Card>
  <Card title="Sending Authenticated Bots" href="/docs/api-v2/authenticated-bots/meet/sending-authenticated-bots">
    Use `meet_config` to send authenticated bots, manage pools, configure fallback, and monitor utilization.
  </Card>
</Cards>

## FAQ

<Accordions type="single">

<Accordion title="Do I need authenticated bots for every Google Meet?">
No. Anonymous bots join open meetings fine. Use authenticated bots only when a meeting is **restricted to signed-in or in-organization users**, or when you need to **bypass the waiting room**. Leave `meet_config` `null` for everything else.
</Accordion>

<Accordion title="Can I use my company's main Google Workspace domain?">
Yes — you don't need a separate domain. The rule is that the **Legacy SSO profile must be scoped to a bot-only group or organizational unit**, never applied to your real users (assigning it org-wide would redirect everyone through the bot IdP). Put your bot accounts in a dedicated group/OU and assign the SSO profile to only that scope. A dedicated subdomain like `bots.acme.com` is one clean way to keep bots isolated, but it's optional. See [Setup](/docs/api-v2/authenticated-bots/meet/setup).
</Accordion>

<Accordion title="How many bots can join at once?">
Each meet login supports up to **20 concurrent SSO sessions**, and capacity scales linearly with the number of active logins in a pool. To raise the ceiling, add more logins. Configure a [Meet Login Utilization alert](/docs/api-v2/alerts#meet-login-alerts) so you're warned before you saturate.
</Accordion>

<Accordion title="How do I get bots past the waiting room?">
Put the login's `email_group` (a Google Group) on the meeting's **calendar invite**, and dispatch the bot with that same `email_group`. The bot is then treated as an invited participant and lands in Meet's **verified queue** instead of the waiting room.
</Accordion>

<Accordion title="When should I use credential_id vs email_group?">
Use `email_group` for round-robin load balancing across a pool (recommended — it takes priority when both are set). Use `credential_id` when you need a specific, fixed login for a bot.
</Accordion>

<Accordion title="What happens if the whole pool is busy?">
Bot creation returns `MEET_LOGIN_UNAVAILABLE` when `meet_config.fallback` is `fail` (the default), or the bot silently joins anonymously when `fallback` is `anonymous`. Add logins or set the fallback based on whether an authenticated identity is mandatory.
</Accordion>

<Accordion title="A workspace or login flipped to invalid — what do I do?">
The system auto-disables a resource after a failure (a SAML rejection for workspaces; a bot login failure for logins). Check `last_error_message`, fix the cause (re-upload a matching cert, complete a bot account's first-time interactive "Welcome to Workspace" login, un-suspend the account), then re-enable it with a `PATCH`.
</Accordion>

<Accordion title="I can't sign in to a bot account to finish its welcome flow">
Every new Google Workspace account has to be signed into once, interactively, to complete the **"Welcome to Workspace"** first-run flow — and that has to happen **before** the Legacy SSO profile is assigned to it. Once the profile applies, Google redirects the account's sign-in to Meeting BaaS and its password stops working, so the flow can't be completed by hand. If you're stuck, move the account out of the SSO scope (or set that scope's **Select SSO profile** back to **None**), complete the welcome flow, then re-assign the profile. See [Setup, Step 3](/docs/api-v2/authenticated-bots/meet/setup#step-3--create-the-bot-accounts-and-complete-the-welcome-flow).
</Accordion>

<Accordion title="Can I retrieve the private key later?">
No. `private_key_pem` is encrypted at rest and **never returned** in any response. If you need a new key, rotate the keypair via `PATCH /v2/meet-workspaces/{workspace_id}` and upload the new certificate to Google at the same time.
</Accordion>

<Accordion title="Does this work for Zoom or Microsoft Teams?">
No — `meet_config` is Google Meet only. For Zoom authentication, see [Zoom Integration](/docs/api-v2/authenticated-bots/zoom). For Microsoft Teams authentication, see [Microsoft Teams Authentication](/docs/api-v2/authenticated-bots/teams).
</Accordion>

</Accordions>

## Related resources

- [Meet Workspaces API](/docs/api-v2/reference/meet-workspaces/createMeetWorkspace) — manage SAML SSO configurations
- [Meet Logins API](/docs/api-v2/reference/meet-logins/createMeetLogin) — manage the Workspace user identities bots sign in as
- [Error Codes](/docs/api-v2/error-codes#google-meet-authentication-errors) — `MEET_LOGIN_*` failure reasons
- [Alerts](/docs/api-v2/alerts#meet-login-alerts) — monitor pool utilization and saturation


---

## Sending Authenticated Bots

Use meet_config to send authenticated Google Meet bots, manage round-robin pools, configure fallback behavior, and monitor login pool utilization

### Source: ./content/docs/api-v2/authenticated-bots/meet/sending-authenticated-bots.mdx


# Sending Authenticated Bots

Once you have at least one **active** meet workspace and login (see [Setup](/docs/api-v2/authenticated-bots/meet/setup)), add a `meet_config` object to your `POST /v2/bots` request to make the bot sign in before joining.

## Round-robin pool (recommended)

Assign the bot to the least-loaded active login in a pool by passing `email_group`. This spreads load across all logins sharing that group and is the right default for unattended recording at scale.

```bash
curl -X POST https://api.meetingbaas.com/v2/bots \
  -H "x-meeting-baas-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "bot_name": "Recording Bot",
    "meeting_url": "https://meet.google.com/abc-defg-hij",
    "meet_config": {
      "email_group": "bots@bots.acme.com",
      "fallback": "fail"
    }
  }'
```

To round-robin across **all** of your team's active logins without filtering by group, pass an empty string:

```json
{ "meet_config": { "email_group": "" } }
```

## Pin a specific login

Use `credential_id` to force the bot to use one particular login.

```json
{
  "bot_name": "Recording Bot",
  "meeting_url": "https://meet.google.com/abc-defg-hij",
  "meet_config": {
    "credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}
```

<Callout type="info">
If you set both `email_group` and `credential_id`, **`email_group` wins** — the pool selector takes priority. Use `credential_id` alone when you need a deterministic, fixed identity.
</Callout>

## Fallback behavior

`fallback` controls what happens when no login slot is available (the whole pool is saturated, or no matching active login exists):

| Value | Behavior |
|-------|----------|
| `fail` (default) | Bot creation fails immediately with `MEET_LOGIN_UNAVAILABLE`. Use this when an authenticated identity is mandatory. |
| `anonymous` | The bot silently falls back to an anonymous (non-authenticated) join. Use this when getting *a* bot in matters more than its identity. |

```json
{ "meet_config": { "email_group": "bots@bots.acme.com", "fallback": "anonymous" } }
```

## Landing in the verified queue

Signing in is only half the story for restricted meetings. To **bypass the waiting room**, the bot's identity must be recognized as invited:

1. Put the login's `email_group` (the Google Group) on the meeting's **calendar invite**.
2. Dispatch the bot with that same `email_group`.

The assigned bot is then a member of an invited group and lands in Meet's **verified queue** instead of the waiting room. This is the recommended pattern for fully unattended recording.

## Concurrency and capacity

Each login supports up to **20 concurrent SSO sessions**. The dispatcher:

1. Filters to **active** logins matching your selector.
2. Picks the one with the lowest `active_session_count`.
3. Skips any login already at capacity.

If every candidate is saturated, the request fails with `MEET_LOGIN_UNAVAILABLE` (or falls back to anonymous). To raise the ceiling, **add more logins** to the pool — capacity scales linearly with the number of active logins.

## Monitoring pool utilization

### Configure a utilization alert first (recommended)

Don't wait until bots start failing. Set up a [**Meet Login Utilization** threshold alert](/docs/api-v2/alerts#meet-login-alerts) so you're notified automatically as your pool fills up — for example, alert when utilization reaches **70%**, giving you time to add logins before you hit the ceiling. Pair it with a [**Meet Login Unavailable** operational alert](/docs/api-v2/alerts#meet-login-alerts) so you also hear about it the moment a bot actually fails to get an authenticated slot (`MEET_LOGIN_UNAVAILABLE`).

Both are configured from the **Alerts** section of your dashboard. See [Alerts](/docs/api-v2/alerts#meet-login-alerts) for setup.

### Check utilization on demand

For an ad-hoc or programmatic view, call `GET /v2/meet-logins/utilization` to see live concurrency across your pool. It's cheap to poll and returns uncached, live counters.

```bash
curl https://api.meetingbaas.com/v2/meet-logins/utilization \
  -H "x-meeting-baas-api-key: $API_KEY"
```

```json
{
  "success": true,
  "data": {
    "logins_total": 5,
    "logins_active": 5,
    "logins_invalid": 0,
    "concurrent_sessions": 42,
    "concurrent_capacity": 100,
    "utilization_pct": 42,
    "by_email_group": [
      { "email_group": "bots@bots.acme.com", "logins": 5, "concurrent": 42, "capacity": 100 }
    ]
  }
}
```

| Field | Meaning |
|-------|---------|
| `logins_total` / `logins_active` / `logins_invalid` | Login counts for your team by state. |
| `concurrent_sessions` | Bots currently in flight using your auth pool (sum of `active_session_count` across active logins). |
| `concurrent_capacity` | `logins_active × 20` (the per-login session limit). |
| `utilization_pct` | `concurrent_sessions / concurrent_capacity`, as a percentage. |
| `by_email_group` | The same metrics broken down per pool. |

## Troubleshooting

| Error code | Meaning | What to do |
|------------|---------|------------|
| `MEET_LOGIN_UNAVAILABLE` | No login slot was available (pool saturated or no matching active login) and `fallback` was `fail`. | Add logins, lower concurrency, or set `fallback: "anonymous"`. Watch utilization. |
| `MEET_LOGIN_REQUIRED` | The meeting required a signed-in user but the bot could not authenticate. | Ensure `meet_config` is set and the selected login is `active`. |
| `MEET_LOGIN_FAILED_SAML_REJECTED` | Google rejected the SAML assertion. | Verify the certificate uploaded to Google Admin matches the workspace cert and the SSO profile is configured and assigned. The workspace auto-flips to `invalid`; re-enable after fixing. |
| `MEET_LOGIN_FAILED_TIMEOUT` | The SSO sign-in did not complete in time. | Confirm the user completed the first-time interactive "Welcome to Workspace" login (done before the SSO profile was assigned) and the account isn't suspended; retry. |

See [Error Codes](/docs/api-v2/error-codes#google-meet-authentication-errors) for the full list. These appear in the bot's `bot.failed` webhook and in the bot details `error_code` field.

## Related resources

- [Setup](/docs/api-v2/authenticated-bots/meet/setup) — one-time workspace and login configuration
- [Create a bot](/docs/api-v2/reference/bots/createBot) — full bot creation reference
- [Meet Logins utilization](/docs/api-v2/reference/meet-logins/getMeetLoginUtilization) — pool metrics endpoint


---

## Setup

Create a meet workspace, configure the Legacy SSO profile in Google Admin Console, create and sign in to the bot accounts, then assign the SSO profile and register meet logins

### Source: ./content/docs/api-v2/authenticated-bots/meet/setup.mdx


# Setting Up Google Meet Authentication

Authenticated Meet bots sign in to a Google Workspace **you control** via SAML SSO. Meeting BaaS acts as the SAML Identity Provider (IdP); your Google Workspace is the Service Provider. This guide walks through the one-time setup.

<Callout type="warn">
**Never route real users through the bot IdP.** The Legacy SSO profile you configure below redirects sign-in to Meeting BaaS, so it must apply **only** to your bot accounts — never to your human users. Scope it one of two ways:

- **Dedicated domain or subdomain (recommended):** Use a domain or subdomain you own that is reserved for bots — for example `bots.acme.com` — and configure SSO there.
- **Dedicated organizational unit or group:** If you keep bots on an existing domain, move the bot accounts into a separate organizational unit (or group) and assign the Legacy SSO profile to **only that OU/group**, leaving everyone else on normal sign-in.

Either way, the SSO profile must target the bot accounts exclusively.
</Callout>

## Prerequisites

- A Google Workspace with **super admin** access to the Admin Console.
- Bot accounts isolated from your human users — either on a dedicated domain/subdomain you own and have verified, or in a dedicated organizational unit/group.
- A Meeting BaaS v2 API key with full access.

## Overview

<Steps>
<Step>Create a **meet workspace** (holds the SAML certificate + key).</Step>
<Step>Upload the certificate and configure the **Legacy SSO profile** in Google Admin Console — without assigning it yet.</Step>
<Step>Create the **Google Workspace users** the bots will sign in as, and sign in to each one to complete the **"Welcome to Workspace"** flow.</Step>
<Step>**Assign** the Legacy SSO profile to the bot group/OU — only after those accounts have completed their welcome flow.</Step>
<Step>Register a **meet login** for each user.</Step>
</Steps>

## Step 1 — Create a meet workspace

A meet workspace is the parent resource for one Google Workspace domain. It stores the SAML signing keypair shared by every login attached to it.

You have two options for the keypair:

### Option A — Let the server generate the keypair (recommended)

Pass `generate_keypair: true`. The server creates a self-signed RSA-2048 keypair with 10-year validity.

```bash
curl -X POST https://api.meetingbaas.com/v2/meet-workspaces \
  -H "x-meeting-baas-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Production Workspace",
    "domain": "bots.acme.com",
    "generate_keypair": true
  }'
```

### Option B — Bring your own keypair

Provide `cert_pem` and `private_key_pem` together (mutually exclusive with `generate_keypair`).

```bash
curl -X POST https://api.meetingbaas.com/v2/meet-workspaces \
  -H "x-meeting-baas-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Production Workspace",
    "domain": "bots.acme.com",
    "cert_pem": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
    "private_key_pem": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
  }'
```

Either way, the response includes the `workspace_id` and the `cert_pem` you'll upload to Google. **`private_key_pem` is never returned** — it is encrypted at rest and only retrievable by rotating the keypair.

```json
{
  "success": true,
  "data": {
    "workspace_id": "f0e1d2c3-b4a5-6789-0123-456789abcdef",
    "name": "Acme Production Workspace",
    "domain": "bots.acme.com",
    "cert_pem": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
    "state": "active",
    "created_at": "2026-06-15T10:00:00.000Z",
    "updated_at": "2026-06-15T10:00:00.000Z"
  }
}
```

<Callout type="info">
Each `domain` may exist at most once per team. Creating a second workspace for the same domain returns `409 Conflict`.
</Callout>

## Step 2 — Configure the Legacy SSO profile in Google Admin Console

This is the step that points your Google Workspace at Meeting BaaS as a SAML Identity Provider. You'll do it once per workspace, signed in to the [Google Admin Console](https://admin.google.com) as a **super admin**.

<Steps>

<Step>
### Open the third-party SSO settings

In the Admin Console left nav, go to **Security → Authentication → SSO with third party IdP**.

<ImageZoom
  src={'/assets/meet-sso/1-find-sso-setting.png'}
  alt="Google Admin Console: Security → Authentication → SSO with third party IdP"
  width={3028}
  height={1722}
  className="rounded-lg border"
/>

Under **Third-party SSO profiles**, open the **Legacy SSO Profile** (type `SAML` — it starts out *Disabled*).

<ImageZoom
  src={'/assets/meet-sso/2-open-legacy-profile.png'}
  alt="Third-party SSO profiles list with the Legacy SSO Profile (SAML)"
  width={3024}
  height={1126}
  className="rounded-lg border"
/>
</Step>

<Step>
### Enable the profile and enter the IdP details

On the **Legacy SSO profile** page, fill in the following and **Save**:

| Field | Value |
|-------|-------|
| Enable legacy SSO profile | ✅ Checked |
| Sign-in page URL | `https://api.meetingbaas.com/v2/meet-sso/sign-in` |
| Sign-out page URL | `https://api.meetingbaas.com/v2/meet-sso/sign-out` |
| Verification certificate | Upload the `cert_pem` from [Step 1](#step-1--create-a-meet-workspace) (use **Replace certificate** / **Or upload**) |
| Use a domain specific issuer | ✅ Checked |

<ImageZoom
  src={'/assets/meet-sso/3-configure-profile.png'}
  alt="Legacy SSO profile form: enabled, Meeting BaaS sign-in/sign-out URLs, certificate uploaded, domain-specific issuer enabled"
  width={3012}
  height={1721}
  className="rounded-lg border"
/>

<Callout type="info">
These `/v2/meet-sso/*` URLs are SAML endpoints that Google calls during sign-in — you configure them in Google, you never call them yourself. The certificate you upload here must always match the one stored on the workspace. If you [rotate the keypair](#rotating-the-keypair), upload the new certificate at the same time.
</Callout>
</Step>
</Steps>

<Callout type="warn">
**Do not assign the profile to your bot accounts yet.** As soon as the Legacy SSO profile covers an account, Google stops accepting its password and redirects sign-in to Meeting BaaS — which means you can no longer complete that account's first-run setup by hand. Create the bot accounts and finish their welcome flow first ([Step 3](#step-3--create-the-bot-accounts-and-complete-the-welcome-flow)), then come back and assign the profile in [Step 4](#step-4--assign-the-sso-profile-to-your-bot-group-or-ou-only).
</Callout>

## Step 3 — Create the bot accounts and complete the welcome flow

Do this **before** assigning the SSO profile in [Step 4](#step-4--assign-the-sso-profile-to-your-bot-group-or-ou-only). For **each** Google account the bots will sign in as:

1. Create the user in Google Admin Console (for example `bot1@bots.acme.com`) and note the temporary password.
2. **Sign in to that account yourself, in a browser, using that password**, and complete the entire **"Welcome to Workspace"** first-run flow — accept the terms, set a new password if prompted, and dismiss the onboarding screens until you land on a normal signed-in Google page. A freshly created account that has never been signed into interactively cannot be used programmatically: the meet login flips to `invalid` on first use.
3. Set the account language to **English (United States)** to ensure the sign-in and Meet UIs are in the expected state.

<Callout type="warn">
**Order matters.** Once the Legacy SSO profile is assigned to the account's group/OU, Google redirects its sign-in to Meeting BaaS and the account password no longer works — so the welcome flow can no longer be completed interactively. If you have already assigned the profile, temporarily move the account out of the SSO scope (or set that scope's **Select SSO profile** back to **None**), complete the welcome flow, then assign the profile again.
</Callout>

<Callout type="tip">
Create a **Google Group** (for example `bots@bots.acme.com`) and add the bot users as members. Putting this group on a calendar invite lets the assigned bot land in Meet's **verified queue** and bypass the waiting room. You'll reference this group as `email_group` in Step 5.
</Callout>

## Step 4 — Assign the SSO profile to your bot group (or OU) only

<Callout type="warn">
Only do this once **every** bot account from [Step 3](#step-3--create-the-bot-accounts-and-complete-the-welcome-flow) has been signed into and has completed its **"Welcome to Workspace"** flow. Assigning the profile first locks you out of the interactive sign-in needed to finish that flow.
</Callout>

Back in **Security → Authentication → SSO with third party IdP**, open **Manage SSO profile assignments**. Under **Groups**, pick the group that contains your bot accounts (for example `bots@bots.acme.com`) — or choose the bot **organizational unit**. A new scope starts with **Select SSO profile: None**.

<ImageZoom
  src={'/assets/meet-sso/4-assign-select-group.png'}
  alt="Manage SSO profile assignments: selecting the bots group as the scope"
  width={3020}
  height={1708}
  className="rounded-lg border"
/>

Set **Select SSO profile** to **Legacy SSO profile**, then click **Override** (or **Save**). This applies the bot IdP to that group/OU only and overrides the inherited organization setting.

<ImageZoom
  src={'/assets/meet-sso/5-assign-legacy-profile.png'}
  alt="Assigning the Legacy SSO profile to the bots group and clicking Override"
  width={3026}
  height={1722}
  className="rounded-lg border"
/>

<Callout type="warn">
Assign the profile to the bot group/OU **only**. Never assign it to a scope that contains real users, or they will be redirected through the bot IdP. Changes take a few minutes to take effect.
</Callout>

## Step 5 — Register a meet login per user

Create one meet login for each Workspace user, referencing the `workspace_id` from Step 1.

```bash
curl -X POST https://api.meetingbaas.com/v2/meet-logins \
  -H "x-meeting-baas-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "workspace_id": "f0e1d2c3-b4a5-6789-0123-456789abcdef",
    "name": "Production Bot Pool — Account 1",
    "email": "bot1@bots.acme.com",
    "email_group": "bots@bots.acme.com"
  }'
```

| Field | Required | Notes |
|-------|----------|-------|
| `workspace_id` | ✅ | UUID of the parent workspace. |
| `name` | ✅ | Friendly label. Not unique. |
| `email` | ✅ | The Workspace user's email. Its domain must match the workspace domain (or a subdomain — e.g. `bot1@dev.bots.acme.com` is valid for workspace domain `bots.acme.com`). |
| `email_group` | optional | Google Group address for round-robin pooling and verified-queue admission. Logins sharing the same `email_group` form one pool. Same domain rule applies. |
| `extra` | optional | Free-form JSON for your own tags (filterable on the list endpoint). |

<Callout type="info">
Each `email` may exist at most once per team (`409 Conflict` on duplicates). An `email`/`email_group` whose domain doesn't match the workspace returns `422 Unprocessable Entity`. An unknown `workspace_id` returns `404 Not Found`.
</Callout>

Repeat for each user. Logins sharing an `email_group` form a round-robin pool — add more logins to increase concurrent capacity (each login handles up to 20 concurrent sessions by default).

## You're ready

With at least one **active** workspace and one **active** login, you can send authenticated bots. Continue to [Sending Authenticated Bots](/docs/api-v2/authenticated-bots/meet/sending-authenticated-bots).

## Maintenance

### Rotating the keypair

Rotate a workspace's certificate and key by sending **both** `cert_pem` and `private_key_pem` to `PATCH /v2/meet-workspaces/{workspace_id}`. The new pair takes effect immediately for all logins under the workspace, so **upload the new certificate to Google Admin Console at the same time** — there is a brief window where the stored cert and Google's cert must match.

### Re-enabling an invalid resource

When a workspace or login flips to `invalid`, fix the underlying cause (re-upload a matching cert, complete a user's interactive "Welcome to Workspace" login — temporarily removing it from the SSO scope if needed, un-suspend the account), then re-enable it with a `PATCH` (`PATCH /v2/meet-workspaces/{workspace_id}` or `PATCH /v2/meet-logins/{credential_id}`). Check `last_error_message` for the reason.

### Deleting a workspace

`DELETE /v2/meet-workspaces/{workspace_id}` **cascades to all of its logins**. Delete a single login with `DELETE /v2/meet-logins/{credential_id}`.


---

## Speaking Bots API Reference

API reference documentation for Speaking Bots

### Source: ./content/docs/speaking-bots/reference/index.mdx


# Speaking Bots API Reference

This section contains detailed documentation for the Speaking Bots API, which allows you to programmatically create and manage speaking bots in your meetings.

The Speaking Bots API provides endpoints to:

- Have bots join meetings
- Make bots leave meetings
- Control bot behavior during meetings
- Generate persona images for bots

Each endpoint is documented with:

- Endpoint URL and method
- Request parameters and body schema
- Response details
- Example requests and responses

All API requests require a MeetingBaas API key to be passed in the `x-meeting-baas-api-key` header.

Use the navigation to explore the available endpoints.


---

## Microsoft Teams Authentication

Send authenticated Microsoft Teams bots that sign in as a Microsoft 365 user with stored credentials, enter sign-in-restricted meetings, and get admitted past the lobby

### Source: ./content/docs/api-v2/authenticated-bots/teams/index.mdx


# Authenticated Microsoft Teams Bots

By default, Meeting BaaS bots join Microsoft Teams as anonymous guests. That works for open meetings, but it falls short when a meeting is **restricted to signed-in users**, restricted to the organizer's organization, or sends guests to a **lobby**.

Authenticated Teams bots solve this. Each bot signs in as a real **Microsoft 365 user** from a tenant you control, using **stored credentials (email + password)**, before it joins the call. To Teams, the bot looks like any other signed-in participant.

<Callout type="info">
Teams authentication uses a **username + password** sign-in on `login.microsoftonline.com` — not SAML SSO like Google Meet. That means **no identity provider, no certificate, and no keypair** to configure. The one requirement is that the account is provisioned **MFA-free** (see [Setup](/docs/api-v2/authenticated-bots/teams/setup)). Leave `teams_config` `null` for anonymous Teams joins.
</Callout>

## Why authenticate

| Scenario | Anonymous bot | Authenticated bot |
|----------|---------------|-------------------|
| Open meeting, anyone with link | ✅ Joins | ✅ Joins |
| "Only people in my org can bypass the lobby" | ⏳ Waits in lobby | ✅ Admitted as an org user |
| Meeting restricted to signed-in users | ❌ `TEAMS_LOGIN_REQUIRED` | ✅ Joins as a signed-in user |
| Recording as a named, consistent identity | ❌ Anonymous guest | ✅ Joins under the account's display name |

## How it works

Authentication is built on three resources. You configure the first two once, then reference them per bot.

<Steps>
<Step>
**Teams Workspace** — the parent resource representing one Microsoft 365 tenant. It groups the logins under a single tenant domain. Unlike a Meet workspace, it holds **no certificate or keypair** — Teams sign-in is credential-based. You create one per Microsoft 365 tenant. See [Setup](/docs/api-v2/authenticated-bots/teams/setup).
</Step>
<Step>
**Teams Logins** — one per Microsoft 365 account the bots sign in as, storing the account's `email` and `password` (the password is encrypted at rest and never returned). Many logins can share a single workspace, and can be grouped into round-robin pools by `email_group`.
</Step>
<Step>
**`teams_config` on the bot** — when you create a bot, you tell it which login (or pool) to use. The dispatcher resolves the credentials, the bot types them into `login.microsoftonline.com`, and then joins the meeting as the signed-in user.
</Step>
</Steps>

```text
Teams Workspace (Microsoft 365 tenant domain)
        │
        ├── Teams Login  bot1@acme.onmicrosoft.com   ┐
        ├── Teams Login  bot2@acme.onmicrosoft.com   ├─ email_group: bots@acme.onmicrosoft.com (round-robin pool)
        └── Teams Login  bot3@acme.onmicrosoft.com   ┘
                                │
        POST /v2/bots  { teams_config: { email_group: "bots@acme.onmicrosoft.com" } }
                                │
                 least-loaded active login is assigned → bot signs in → joins Teams
```

## The `teams_config` object

All Teams authentication options are passed in a single `teams_config` object on `POST /v2/bots`:

```json
{
  "bot_name": "Recording Bot",
  "meeting_url": "https://teams.microsoft.com/meet/1234567890?p=AbCdEfGhIj",
  "teams_config": {
    "email_group": "bots@acme.onmicrosoft.com",
    "fallback": "fail"
  }
}
```

| Parameter | Description |
|-----------|-------------|
| `email_group` | Round-robin pool selector. The bot is assigned the **least-loaded active login** in this pool. Preferred for most use cases — **takes priority over `credential_id`**. Pass `""` to round-robin across all of the team's active logins. |
| `credential_id` | Pin one specific login (UUID) for this bot. |
| `fallback` | What to do when no login slot is available: `fail` (default) fails bot creation with `TEAMS_LOGIN_UNAVAILABLE`; `anonymous` silently falls back to an anonymous join. |

Leave `teams_config` `null` for anonymous Teams joins, Zoom, or Google Meet.

## Key concepts

### Getting past the lobby

Signing in is what gets the bot admitted. When the account belongs to the **organizer's organization** (or the meeting's lobby policy admits people in the org), the authenticated bot is let in automatically instead of waiting in the lobby as an anonymous guest. For meetings restricted to signed-in users, an authenticated bot is the only way in — an anonymous bot fails with `TEAMS_LOGIN_REQUIRED`.

### MFA must be off

The bot types the account's password on `login.microsoftonline.com`. If the tenant forces multi-factor authentication or security-info registration, sign-in stalls on the **"Let's keep your account secure"** page and the login fails. The bot accounts must therefore be provisioned **MFA-free** — turn off Security Defaults or exclude the bot accounts from your MFA Conditional Access policy. This is the single most important part of [Setup](/docs/api-v2/authenticated-bots/teams/setup).

### Round-robin pools and concurrency

Each login supports up to **20 concurrent sessions**. When you dispatch bots with an `email_group`, the assigner picks the least-loaded active login and skips any login at capacity. If every login in a pool is saturated, bot creation fails with `TEAMS_LOGIN_UNAVAILABLE` (or falls back to anonymous, per your `fallback` setting). Create more logins to raise the ceiling, and configure a [utilization alert](/docs/api-v2/alerts) to stay ahead of saturation (the [utilization endpoint](/docs/api-v2/authenticated-bots/teams/sending-authenticated-bots#monitoring-pool-utilization) gives an on-demand view).

### States

Both workspaces and logins track health with a `state` field:

- **`active`** — healthy and usable.
- **`invalid`** — the system auto-disabled the resource after a failure (a bad-credentials rejection, an MFA/security-info prompt, or a login timeout). Re-enable manually via `PATCH` after fixing the underlying issue.

When a resource flips to `invalid`, `last_error_message` and `last_error_at` explain why.

### Security

Account passwords are encrypted at rest using **AES-256-GCM**. The `password` is **write-only** — never returned in any API response, on create or on subsequent reads. To change it, send a new `password` via `PATCH /v2/teams-logins/{credential_id}`.

## Pages in this section

<Cards>
  <Card title="Setup" href="/docs/api-v2/authenticated-bots/teams/setup">
    Provision an MFA-free Microsoft 365 account, create a teams workspace, and register teams logins.
  </Card>
  <Card title="Sending Authenticated Bots" href="/docs/api-v2/authenticated-bots/teams/sending-authenticated-bots">
    Use `teams_config` to send authenticated bots, manage pools, configure fallback, and monitor utilization.
  </Card>
</Cards>

## FAQ

<Accordions type="single">

<Accordion title="Do I need authenticated bots for every Microsoft Teams meeting?">
No. Anonymous bots join open meetings fine. Use authenticated bots only when a meeting is **restricted to signed-in or in-organization users**, or when you need to **be admitted past the lobby** as an org member. Leave `teams_config` `null` for everything else.
</Accordion>

<Accordion title="How is this different from Google Meet authentication?">
Google Meet uses **SAML SSO** — Meeting BaaS acts as an identity provider and you configure a Legacy SSO profile plus a signing certificate in the Google Admin Console. Microsoft Teams uses a plain **username + password** sign-in, so there is no IdP, no certificate, and no keypair. The trade-off is that you must provision the account **MFA-free** and store its password (encrypted at rest).
</Accordion>

<Accordion title="Why does the account have to be MFA-free?">
The bot signs in by typing the password on `login.microsoftonline.com`. Any forced multi-factor or security-info step (the "Let's keep your account secure" page) has no human to complete it, so the login stalls and fails with `TEAMS_LOGIN_FAILED_MFA_REQUIRED`. Turn off Security Defaults, or exclude the bot accounts from your MFA Conditional Access policy. Keep MFA on for your real users. See [Setup](/docs/api-v2/authenticated-bots/teams/setup).
</Accordion>

<Accordion title="How many bots can join at once?">
Each teams login supports up to **20 concurrent sessions**, and capacity scales linearly with the number of active logins in a pool. To raise the ceiling, add more logins. Configure a [utilization alert](/docs/api-v2/alerts) so you're warned before you saturate.
</Accordion>

<Accordion title="When should I use credential_id vs email_group?">
Use `email_group` for round-robin load balancing across a pool (recommended — it takes priority when both are set). Use `credential_id` when you need a specific, fixed login for a bot.
</Accordion>

<Accordion title="What happens if the whole pool is busy?">
Bot creation returns `TEAMS_LOGIN_UNAVAILABLE` when `teams_config.fallback` is `fail` (the default), or the bot silently joins anonymously when `fallback` is `anonymous`. Add logins or set the fallback based on whether an authenticated identity is mandatory.
</Accordion>

<Accordion title="A login flipped to invalid — what do I do?">
The system auto-disables a login after a failure (bad credentials, an MFA/security-info prompt, or a sign-in timeout). Check `last_error_message`, fix the cause (reset and update the password, turn off MFA for the account, un-suspend it), then re-enable it with a `PATCH`.
</Accordion>

<Accordion title="Can I retrieve the account password later?">
No. The `password` is encrypted at rest and **never returned** in any response. If it changes, send the new value via `PATCH /v2/teams-logins/{credential_id}`.
</Accordion>

</Accordions>

## Related resources

- [Teams Workspaces API](/docs/api-v2/reference/teams-workspaces/createTeamsWorkspace) — manage the Microsoft 365 tenant grouping
- [Teams Logins API](/docs/api-v2/reference/teams-logins/createTeamsLogin) — manage the Microsoft 365 identities bots sign in as
- [Error Codes](/docs/api-v2/error-codes) — `TEAMS_LOGIN_*` failure reasons
- [Alerts](/docs/api-v2/alerts) — monitor pool utilization and saturation


---

## Sending Authenticated Bots

Use teams_config to send authenticated Microsoft Teams bots, manage round-robin pools, configure fallback behavior, and monitor login pool utilization

### Source: ./content/docs/api-v2/authenticated-bots/teams/sending-authenticated-bots.mdx


# Sending Authenticated Bots

Once you have at least one **active** teams workspace and login (see [Setup](/docs/api-v2/authenticated-bots/teams/setup)), add a `teams_config` object to your `POST /v2/bots` request to make the bot sign in before joining.

## Round-robin pool (recommended)

Assign the bot to the least-loaded active login in a pool by passing `email_group`. This spreads load across all logins sharing that group and is the right default for unattended recording at scale.

```bash
curl -X POST https://api.meetingbaas.com/v2/bots \
  -H "x-meeting-baas-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "bot_name": "Recording Bot",
    "meeting_url": "https://teams.microsoft.com/meet/1234567890?p=AbCdEfGhIj",
    "teams_config": {
      "email_group": "bots@acme.onmicrosoft.com",
      "fallback": "fail"
    }
  }'
```

To round-robin across **all** of your team's active logins without filtering by group, pass an empty string:

```json
{ "teams_config": { "email_group": "" } }
```

## Pin a specific login

Use `credential_id` to force the bot to use one particular login.

```json
{
  "bot_name": "Recording Bot",
  "meeting_url": "https://teams.microsoft.com/meet/1234567890?p=AbCdEfGhIj",
  "teams_config": {
    "credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}
```

<Callout type="info">
If you set both `email_group` and `credential_id`, **`email_group` wins** — the pool selector takes priority. Use `credential_id` alone when you need a deterministic, fixed identity.
</Callout>

## Fallback behavior

`fallback` controls what happens when no login slot is available (the whole pool is saturated, or no matching active login exists):

| Value | Behavior |
|-------|----------|
| `fail` (default) | Bot creation fails immediately with `TEAMS_LOGIN_UNAVAILABLE`. Use this when an authenticated identity is mandatory. |
| `anonymous` | The bot silently falls back to an anonymous (non-authenticated) join. Use this when getting *a* bot in matters more than its identity. |

```json
{ "teams_config": { "email_group": "bots@acme.onmicrosoft.com", "fallback": "anonymous" } }
```

## Getting admitted past the lobby

Signing in is what gets the bot admitted. When the login account belongs to the **organizer's organization** — or the meeting's lobby policy admits people in the org — the authenticated bot is let in automatically instead of waiting as an anonymous guest. For meetings restricted to signed-in users, an authenticated bot is the only way in; an anonymous bot fails with `TEAMS_LOGIN_REQUIRED`. Use accounts in (or federated with) the organizer's tenant when you need reliable, unattended admission.

## Concurrency and capacity

Each login supports up to **20 concurrent sessions**. The dispatcher:

1. Filters to **active** logins matching your selector.
2. Picks the one with the lowest `active_session_count`.
3. Skips any login already at capacity.

If every candidate is saturated, the request fails with `TEAMS_LOGIN_UNAVAILABLE` (or falls back to anonymous). To raise the ceiling, **add more logins** to the pool — capacity scales linearly with the number of active logins.

## Monitoring pool utilization

### Configure a utilization alert first (recommended)

Don't wait until bots start failing. Set up a **login utilization** threshold alert so you're notified automatically as your pool fills up — for example, alert when utilization reaches **70%**, giving you time to add logins before you hit the ceiling. Pair it with an operational alert on `TEAMS_LOGIN_UNAVAILABLE` so you also hear about it the moment a bot actually fails to get an authenticated slot. Both are configured from the **Alerts** section of your dashboard. See [Alerts](/docs/api-v2/alerts).

### Check utilization on demand

For an ad-hoc or programmatic view, call `GET /v2/teams-logins/utilization` to see live concurrency across your pool. It's cheap to poll and returns uncached, live counters.

```bash
curl https://api.meetingbaas.com/v2/teams-logins/utilization \
  -H "x-meeting-baas-api-key: $API_KEY"
```

```json
{
  "success": true,
  "data": {
    "logins_total": 5,
    "logins_active": 5,
    "logins_invalid": 0,
    "concurrent_sessions": 42,
    "concurrent_capacity": 100,
    "utilization_pct": 42,
    "by_email_group": [
      { "email_group": "bots@acme.onmicrosoft.com", "logins": 5, "concurrent": 42, "capacity": 100 }
    ]
  }
}
```

| Field | Meaning |
|-------|---------|
| `logins_total` / `logins_active` / `logins_invalid` | Login counts for your team by state. |
| `concurrent_sessions` | Bots currently in flight using your auth pool (sum of `active_session_count` across active logins). |
| `concurrent_capacity` | `logins_active × 20` (the per-login session limit). |
| `utilization_pct` | `concurrent_sessions / concurrent_capacity`, as a percentage. |
| `by_email_group` | The same metrics broken down per pool. |

## Troubleshooting

| Error code | Meaning | What to do |
|------------|---------|------------|
| `TEAMS_LOGIN_UNAVAILABLE` | No login slot was available (pool saturated or no matching active login) and `fallback` was `fail`. | Add logins, lower concurrency, or set `fallback: "anonymous"`. Watch utilization. |
| `TEAMS_LOGIN_REQUIRED` | The meeting required a signed-in user but the bot could not authenticate. | Ensure `teams_config` is set and the selected login is `active`. |
| `TEAMS_LOGIN_FAILED_BAD_CREDENTIALS` | Microsoft rejected the email/password. | Update the stored `password` via `PATCH /v2/teams-logins/{credential_id}` and confirm the account isn't locked. The login auto-flips to `invalid`; re-enable after fixing. |
| `TEAMS_LOGIN_FAILED_MFA_REQUIRED` | Sign-in hit the "Let's keep your account secure" / MFA page. | Make the account MFA-free (turn off Security Defaults or exclude it from your MFA policy), complete one interactive sign-in, then re-enable the login. |
| `TEAMS_LOGIN_FAILED_TIMEOUT` | The sign-in did not complete in time. | Confirm the account isn't suspended and MFA is off; retry. |

See [Error Codes](/docs/api-v2/error-codes) for the full list. These appear in the bot's `bot.failed` webhook and in the bot details `error_code` field.

## Related resources

- [Setup](/docs/api-v2/authenticated-bots/teams/setup) — one-time workspace and login configuration
- [Create a bot](/docs/api-v2/reference/bots/createBot) — full bot creation reference
- [Teams Logins utilization](/docs/api-v2/reference/teams-logins/getTeamsLoginUtilization) — pool metrics endpoint


---

## Setup

Provision an MFA-free Microsoft 365 account, create a teams workspace, and register teams logins for authenticated Microsoft Teams bots

### Source: ./content/docs/api-v2/authenticated-bots/teams/setup.mdx


# Setting Up Microsoft Teams Authentication

Authenticated Teams bots sign in to a Microsoft 365 account **you control** with a stored email and password. Unlike Google Meet, there is **no SAML identity provider, no certificate, and no keypair** to configure — but the account must be provisioned so a bot can complete sign-in unattended. This guide walks through the one-time setup.

<Callout type="warn">
**The bot account must be MFA-free.** The bot signs in by typing the password on `login.microsoftonline.com`. If your tenant forces multi-factor authentication or security-info registration, sign-in stalls on the **"Let's keep your account secure"** page and fails. Provision the bot accounts so they never hit that page — and keep MFA **on** for your real users. Scope it one of two ways:

- **Turn off Security Defaults (simplest):** For a tenant dedicated to bots, disable Security Defaults so no account is forced into MFA.
- **Exclude the bots from a Conditional Access policy (recommended for mixed tenants):** Keep MFA for humans, put the bot accounts in a dedicated group (for example `svc-teams-bots`), and **exclude that group** from your "require MFA" policy. Requires Microsoft Entra ID P1.

Either way, the bot accounts must reach a password-only sign-in with no security-info prompt.

</Callout>

## Prerequisites

- A Microsoft 365 tenant with **admin** access to the [Microsoft Entra admin center](https://entra.microsoft.com) and [Microsoft 365 admin center](https://admin.microsoft.com).
- Bot accounts isolated from your human users — a dedicated tenant, or a dedicated group excluded from MFA.
- A Microsoft Teams license for each bot account (so it can join and be admitted like a member).
- A Meeting BaaS v2 API key with full access.

## Overview

<Steps>
  <Step>
    Create a **Microsoft 365 account** for the bot and assign it a Teams
    license.
  </Step>
  <Step>
    Make the account **MFA-free** (Security Defaults off, or exclude it from
    your MFA policy).
  </Step>
  <Step>Complete the account's **first interactive sign-in** once.</Step>
  <Step>
    Create a **teams workspace** (the tenant grouping) and register a **teams
    login** per account.
  </Step>
</Steps>

## Step 1 — Create the Microsoft 365 account

The simplest path is the **[Microsoft 365 admin center](https://admin.microsoft.com)** → **Users → Active users → Add a user**: set a clear username (for example `bot1@acme.onmicrosoft.com`) and a strong password you'll store with Meeting BaaS (uncheck **"Require this user to change their password"**) — this wizard can also assign the license in the same flow (next). The new account then shows up under **Active users**:

<ImageZoom
  src={'/assets/teams-sso/1-create-user-m365.png'}
  alt="Microsoft 365 admin center → Users → Active users, showing the created bot account"
  width={2400}
  height={1136}
  className="rounded-lg border"
/>

You can also create it in the **[Microsoft Entra admin center](https://entra.microsoft.com)** → **Users → All users → + New user → Create new user**. In Entra, "Users" sits under the **Identity** group in the left nav; if you don't see it, click **Show more** or type **Users** in the top search bar.

<ImageZoom
  src={'/assets/teams-sso/2-create-user-entra.png'}
  alt="Microsoft Entra admin center → Users → the New user button in the command bar"
  width={2400}
  height={1136}
  className="rounded-lg border"
/>

Then give the account a **Microsoft Teams** license (part of most Microsoft 365 / Office 365 plans): in the [Microsoft 365 admin center](https://admin.microsoft.com), select the bot in **Users → Active users** and click **Manage product licenses** (you can also do this inside the **Add a user** wizard above). A bot without a Teams license can't be admitted as an organization member.

<ImageZoom
  src={'/assets/teams-sso/3-assign-license.png'}
  alt="Microsoft 365 admin center: bot selected, Manage product licenses in the command bar"
  width={2400}
  height={1136}
  className="rounded-lg border"
/>

<Callout type="tip">
  Put your bot accounts in a dedicated group (for example `svc-teams-bots`).
  You'll use it for the MFA setup in Step 2, and you can reuse its
  address as the `email_group` for round-robin pooling in Step 4.
</Callout>

## Step 2 — Make the account MFA-free

The bot signs in by typing the password on `login.microsoftonline.com`, so anything that forces multi-factor or security-info registration stalls it on the **"Let's keep your account secure"** page. Two things can force that on a Teams sign-in today:

1. **Security Defaults** — the tenant-wide toggle that makes everyone register for and use MFA.
2. **Conditional Access "require MFA" policies** — including the **Microsoft-managed** ones Entra now auto-creates on licensed tenants (see Option C).

<Callout type="info">
Microsoft's 2024–2025 **mandatory MFA** rollout only covers the **admin portals and Azure resource management** — it does **not** block a bot's interactive sign-in to Teams/Office, so that mandate isn't your blocker. The two items above are.
</Callout>

Pick the option that matches your tenant.

<Steps>

<Step>
### Option A — Disable Security Defaults (free / dedicated bot tenant)

If the tenant has **no** Entra ID P1/P2 or Microsoft 365 Business Premium license (so no Conditional Access), turning off Security Defaults is enough.

1. Go to **Entra ID → Overview → Properties**, scroll to the bottom and open **Manage security defaults**, set **Security defaults** to **Disabled (not recommended)**, and **Save**. Some tenants label the top node **Identity** instead of **Entra ID** — it's the same **Properties** page. (Requires the **Conditional Access Administrator** role.)
2. Set the bot's per-user MFA state to **Disabled**: **Identity → Users → All users → Per-user MFA** (in the command bar, or under the **···** menu) → select the account → **Disabled** → **Save**. (`Disabled` is the default, so usually it's already set.)

<ImageZoom
  src={'/assets/teams-sso/4-disable-security-defaults.png'}
  alt="Microsoft Entra admin center: Entra ID → Overview → Properties → Manage security defaults set to Disabled"
  width={2400}
  height={1137}
  className="rounded-lg border"
/>

<Callout type="warn">
Only disable Security Defaults on a tenant **dedicated to bots** — it drops baseline MFA for everyone in the tenant. On a tenant with real users, use **Option B**.
</Callout>
</Step>

<Step>
### Option B — Turn MFA off for just the bot (per-user MFA — easiest way to keep people on MFA)

Keep MFA on for your real users and switch it off for the bot only — no Conditional Access or premium license needed. First make sure **Security Defaults** is **Off** (Option A, step 1), since per-user MFA is ignored while Security Defaults is on. Then, in the **[Microsoft 365 admin center](https://admin.microsoft.com)** → **Users → Active users**, tick the bot, click **Multi-factor authentication** in the command bar, select the account, and set its status to **Disabled**.

<ImageZoom
  src={'/assets/teams-sso/5-per-user-mfa.png'}
  alt="Microsoft 365 admin center → Active users → the Multi-factor authentication button (set the bot's per-user MFA to Disabled)"
  width={2400}
  height={1134}
  className="rounded-lg border"
/>
</Step>

<Step>
### Option C — Exclude the bot via Conditional Access (tenants that enforce MFA with CA)

If your tenant enforces MFA through **Conditional Access** — including the **Microsoft-managed** policies Entra auto-creates on **Entra ID P1/P2 or Business Premium** tenants — per-user MFA won't exempt the bot; you have to exclude it from those policies.

1. Make sure **Security Defaults** is **Off** (Option A, step 1) — Conditional Access and Security Defaults can't both be on.
2. Put the bot accounts in a dedicated group, e.g. `svc-teams-bots`.
3. Exclude that group from **every** "require MFA" policy — your own **and the Microsoft-managed** ones (rows with **Created by = Microsoft**, e.g. *"Multifactor authentication for all users"*): **Protection → Conditional Access → Policies** → open each MFA policy → **Assignments → Users → Exclude → Users and groups** → add `svc-teams-bots` → **Save**. You can't delete the Microsoft-managed policies, but you can exclude a group or set them **Off**.

<Callout type="warn">
**Re-check this periodically.** On P2 / Business Premium tenants, Microsoft auto-creates Conditional Access MFA policies in **report-only** and **auto-enables them ~45 days later**. If a new *"Multifactor authentication for all users"* policy appears, exclude your bot group from it (or set it Off) before it turns on — otherwise the bot starts failing sign-in weeks later.
</Callout>
</Step>

</Steps>

The end state, either way: signing in with the bot account is **email → password → "Stay signed in?"** with **no** "Let's keep your account secure" prompt in between — verify in [Step 3](#step-3--complete-the-first-interactive-sign-in).

## Step 3 — Complete the first interactive sign-in

Sign in to the bot account **once** interactively at [https://login.microsoftonline.com](https://login.microsoftonline.com) and click through **"Stay signed in?"**. This clears any first-run interstitials so the bot's automated sign-in reaches the password step cleanly. If you still see a "keep your account secure" page here, MFA is not fully off — revisit Step 2.

<Callout type="tip">
  Set the account's language to **English (United States)** so the sign-in and
  Teams UIs are in the expected state.
</Callout>

## Step 4 — Register the workspace and logins

### Create a teams workspace

A teams workspace groups all logins for one Microsoft 365 tenant. It stores no secrets — just the tenant domain.

```bash
curl -X POST https://api.meetingbaas.com/v2/teams-workspaces \
  -H "x-meeting-baas-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Bots Tenant",
    "domain": "acme.onmicrosoft.com"
  }'
```

| Field    | Required | Notes                                                                                                      |
| -------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| `domain` | ✅       | The Microsoft 365 tenant primary domain (for example `acme.onmicrosoft.com`, or a verified custom domain). |
| `name`   | optional | Friendly label. Not unique.                                                                                |
| `extra`  | optional | Free-form JSON for your own tags.                                                                          |

The response includes the `workspace_id` you'll reference when creating logins:

```json
{
  "success": true,
  "data": {
    "workspace_id": "f0e1d2c3-b4a5-6789-0123-456789abcdef",
    "name": "Acme Bots Tenant",
    "domain": "acme.onmicrosoft.com",
    "state": "active",
    "created_at": "2026-07-29T10:00:00.000Z",
    "updated_at": "2026-07-29T10:00:00.000Z"
  }
}
```

### Register a teams login per account

Create one teams login for each Microsoft 365 account, referencing the `workspace_id` above. The `password` is encrypted at rest and never returned.

```bash
curl -X POST https://api.meetingbaas.com/v2/teams-logins \
  -H "x-meeting-baas-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "workspace_id": "f0e1d2c3-b4a5-6789-0123-456789abcdef",
    "name": "Production Bot Pool — Account 1",
    "email": "bot1@acme.onmicrosoft.com",
    "password": "the-account-password",
    "email_group": "bots@acme.onmicrosoft.com"
  }'
```

| Field          | Required | Notes                                                                                      |
| -------------- | -------- | ------------------------------------------------------------------------------------------ |
| `workspace_id` | ✅       | UUID of the parent teams workspace.                                                        |
| `name`         | ✅       | Friendly label. Not unique.                                                                |
| `email`        | ✅       | The Microsoft 365 account the bot signs in as.                                             |
| `password`     | ✅       | The account password. **Write-only** — encrypted at rest (AES-256-GCM) and never returned. |
| `email_group`  | optional | Address for round-robin pooling. Logins sharing the same `email_group` form one pool.      |
| `extra`        | optional | Free-form JSON for your own tags (filterable on the list endpoint).                        |

<Callout type="info">
  Each `email` may exist at most once per team (`409 Conflict` on duplicates).
  An unknown `workspace_id` returns `404 Not Found`.
</Callout>

Repeat for each account. Logins sharing an `email_group` form a round-robin pool — add more logins to increase concurrent capacity (each login handles up to 20 concurrent sessions by default).

## You're ready

With at least one **active** workspace and one **active** login, you can send authenticated bots. Continue to [Sending Authenticated Bots](/docs/api-v2/authenticated-bots/teams/sending-authenticated-bots).

## Maintenance

### Rotating the password

When an account's password changes, send the new value to `PATCH /v2/teams-logins/{credential_id}`. It's re-encrypted at rest immediately; keep the Microsoft 365 account and the stored login in sync so the bot can sign in.

### Re-enabling an invalid login

When a login flips to `invalid`, fix the underlying cause (update the password, turn MFA off for the account, un-suspend it), then re-enable it with a `PATCH` (`PATCH /v2/teams-logins/{credential_id}`). Check `last_error_message` for the reason.

### Deleting a workspace

`DELETE /v2/teams-workspaces/{workspace_id}` **cascades to all of its logins**. Delete a single login with `DELETE /v2/teams-logins/{credential_id}`.


---

## Zoom Credentials

Store and manage Zoom SDK credentials and OAuth tokens securely with the v2 Credentials API

### Source: ./content/docs/api-v2/authenticated-bots/zoom/credentials.mdx


# Zoom Credentials API

The v2 Credentials API provides secure storage for your Zoom app credentials and OAuth tokens. Instead of passing SDK credentials with every bot request, you can store them once and reference them by ID.

## Overview

The `/v2/zoom-credentials` endpoint lets you:

- Store Zoom app credentials (SDK client ID and secret)
- Exchange OAuth authorization codes for tokens
- Manage multiple credentials for different Zoom users
- Track credential health with state and error tracking

All credentials are encrypted at rest using **AES-256-GCM**. Secrets and tokens are never returned in API responses.

## Credential Types

### App-Only Credentials

Store your Zoom app's SDK credentials. Use these when your bots only join meetings within your own Zoom organization (internal meetings).

**What's stored:**
- Client ID (SDK Key)
- Client Secret (SDK Secret)

**Use case:** Recording your team's meetings without OBF tokens.

### User Credentials

Store OAuth tokens for a specific Zoom user who authorized your app. Use these for OBF (On Behalf Of) token support when joining external meetings.

**What's stored:**
- Client ID and Secret
- Access token (encrypted)
- Refresh token (encrypted)
- Zoom user ID and account ID
- Zoom email and display name (captured from Zoom's `/users/me` API at OAuth time, requires the `user:read:user` scope)
- Granted scopes
- Optional `extra` JSON object you supply to tag the credential (e.g. internal user ID, environment)

**Use case:** Building a product where customers authorize your bot to join their meetings.

## Creating Credentials

### App-Only Credentials

Store SDK credentials for internal meeting access:

<Tabs items={['cURL', 'Python', 'JavaScript']}>
  <Tab value="cURL">
    ```bash
    curl -X POST "https://api.meetingbaas.com/v2/zoom-credentials" \
         -H "Content-Type: application/json" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY" \
         -d '{
               "name": "Production Zoom App",
               "client_id": "YOUR_ZOOM_CLIENT_ID",
               "client_secret": "YOUR_ZOOM_CLIENT_SECRET"
             }'
    ```
  </Tab>
  <Tab value="Python">
    ```python
    import requests

    response = requests.post(
        "https://api.meetingbaas.com/v2/zoom-credentials",
        headers={
            "Content-Type": "application/json",
            "x-meeting-baas-api-key": "YOUR-API-KEY",
        },
        json={
            "name": "Production Zoom App",
            "client_id": "YOUR_ZOOM_CLIENT_ID",
            "client_secret": "YOUR_ZOOM_CLIENT_SECRET"
        }
    )
    print(response.json())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript
    const response = await fetch("https://api.meetingbaas.com/v2/zoom-credentials", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
      },
      body: JSON.stringify({
        name: "Production Zoom App",
        client_id: "YOUR_ZOOM_CLIENT_ID",
        client_secret: "YOUR_ZOOM_CLIENT_SECRET",
      }),
    });
    console.log(await response.json());
    ```
  </Tab>
</Tabs>

**Response:**

```json
{
  "success": true,
  "data": {
    "credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "Production Zoom App",
    "credential_type": "app",
    "zoom_user_id": null,
    "zoom_account_id": null,
    "zoom_email": null,
    "zoom_display_name": null,
    "scopes": null,
    "state": "active",
    "last_error_message": null,
    "last_error_at": null,
    "extra": null,
    "created_at": "2026-02-10T10:00:00Z",
    "updated_at": "2026-02-10T10:00:00Z"
  }
}
```

Save the `credential_id` — you'll use it when creating bots.

### User Credentials (with OAuth)

After a user completes the OAuth consent flow, exchange the authorization code for tokens:

<Tabs items={['cURL', 'Python', 'JavaScript']}>
  <Tab value="cURL">
    ```bash
    curl -X POST "https://api.meetingbaas.com/v2/zoom-credentials" \
         -H "Content-Type: application/json" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY" \
         -d '{
               "name": "John Doe - Acme Corp",
               "client_id": "YOUR_ZOOM_CLIENT_ID",
               "client_secret": "YOUR_ZOOM_CLIENT_SECRET",
               "authorization_code": "AUTHORIZATION_CODE_FROM_ZOOM",
               "redirect_uri": "https://your-app.com/oauth/callback"
             }'
    ```
  </Tab>
  <Tab value="Python">
    ```python
    import requests

    response = requests.post(
        "https://api.meetingbaas.com/v2/zoom-credentials",
        headers={
            "Content-Type": "application/json",
            "x-meeting-baas-api-key": "YOUR-API-KEY",
        },
        json={
            "name": "John Doe - Acme Corp",
            "client_id": "YOUR_ZOOM_CLIENT_ID",
            "client_secret": "YOUR_ZOOM_CLIENT_SECRET",
            "authorization_code": "AUTHORIZATION_CODE_FROM_ZOOM",
            "redirect_uri": "https://your-app.com/oauth/callback"
        }
    )
    print(response.json())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript
    const response = await fetch("https://api.meetingbaas.com/v2/zoom-credentials", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
      },
      body: JSON.stringify({
        name: "John Doe - Acme Corp",
        client_id: "YOUR_ZOOM_CLIENT_ID",
        client_secret: "YOUR_ZOOM_CLIENT_SECRET",
        authorization_code: "AUTHORIZATION_CODE_FROM_ZOOM",
        redirect_uri: "https://your-app.com/oauth/callback",
      }),
    });
    console.log(await response.json());
    ```
  </Tab>
</Tabs>

**Response:**

```json
{
  "success": true,
  "data": {
    "credential_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
    "name": "John Doe - Acme Corp",
    "credential_type": "user",
    "zoom_user_id": "SeJwoMGwTCu52501SbDC0Q",
    "zoom_account_id": "AplWZ5oMSouJOw9zu0cmKQ",
    "zoom_email": "john.doe@acme.com",
    "zoom_display_name": "John Doe",
    "scopes": "user:read:token,user:read:user,user:read:zak",
    "state": "active",
    "last_error_message": null,
    "last_error_at": null,
    "extra": null,
    "created_at": "2026-02-10T10:00:00Z",
    "updated_at": "2026-02-10T10:00:00Z"
  }
}
```

<Callout>
**Important:** The `redirect_uri` must exactly match the URI registered in your Zoom app and used in the OAuth authorization URL.
</Callout>

<Callout>
**Showing the connected account in your UI:** `zoom_email` and `zoom_display_name` are captured from Zoom's `/users/me` API at OAuth time. Use them to show users which Zoom account is connected — particularly helpful when a user has multiple Zoom accounts and needs to verify the right one is linked.
</Callout>

## Attaching Custom Metadata with `extra`

Both `POST /v2/zoom-credentials` and `PATCH /v2/zoom-credentials/{id}` accept an optional `extra` JSON object that lets you tag the credential with arbitrary key/value pairs your application cares about. The API stores it as-is and never interprets it.

```bash
curl -X POST "https://api.meetingbaas.com/v2/zoom-credentials" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "name": "John Doe - Acme Corp",
           "client_id": "YOUR_ZOOM_CLIENT_ID",
           "client_secret": "YOUR_ZOOM_CLIENT_SECRET",
           "authorization_code": "AUTHORIZATION_CODE_FROM_ZOOM",
           "redirect_uri": "https://your-app.com/oauth/callback",
           "extra": {
             "internal_user_id": "u_42",
             "environment": "production",
             "tenant": "acme"
           }
         }'
```

Common uses:
- Correlate the credential with a record in your own database (e.g. `internal_user_id`)
- Tag credentials with environment, region, or tenant for later filtering
- Store anything else your app needs without maintaining a side table

To clear the metadata on an existing credential, send `"extra": null` in a `PATCH` request.

## Active Apps Notifier (AAN) Attribution

When a bot joins a Zoom meeting, Zoom displays the app name in the **Active Apps Notifier (AAN)** — a notice visible to all participants showing which apps are accessing meeting content. The app name is determined by the **SDK credentials** used to initialize the session.

<Callout type="warn">
**Zoom Marketplace Requirement:** During Marketplace review, Zoom requires the AAN to display **your** app name. If the AAN shows "Meeting Baas" instead of your product name, the reviewer may flag this. See [Zoom's AAN documentation](https://developers.zoom.us/docs/meeting-sdk/ui-notices/#active-apps-notifier-aan-use-case).
</Callout>

### How Credentials Control the AAN

When you store a credential (either `app` or `user` type) and reference it via `credential_id` in `zoom_config`, the bot uses **your app's** SDK credentials for the session. This means the AAN displays your app name instead of "Meeting Baas".

- **With a stored credential**: AAN shows **your** app name
- **Without a credential** (e.g., only `obf_token` or `obf_token_url`): AAN shows "Meeting Baas"

### Combining App-Only Credentials with External OBF

If you manage OBF tokens yourself (via `obf_token` or `obf_token_url`) but still want the AAN to show your app name, store an **app-only credential** and combine it with your OBF method:

```json
{
  "bot_name": "Recording Bot",
  "meeting_url": "https://zoom.us/j/123456789",
  "zoom_config": {
    "credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "obf_token_url": "https://your-api.com/zoom/obf-token"
  }
}
```

This gives you the best of both worlds:
- **Your SDK credentials** from the stored credential control the AAN (your app name)
- **Your OBF backend** continues handling token generation (no changes needed)

You can also manage credentials through the [Meeting BaaS Dashboard](https://app.meetingbaas.com) under the Zoom Credentials section, so you don't need to use the API directly.

To find your SDK credentials in the Zoom Marketplace, see: [Get Meeting SDK Credentials](https://developers.zoom.us/docs/meeting-sdk/get-credentials/#get-meeting-sdk-credentials)

<Callout>
**v1 API users:** In v1, you must pass `zoom_sdk_id` and `zoom_sdk_pwd` with every bot request to control the AAN. v2's credential storage is more secure — store once, reference by ID. See the [Migration Guide](/docs/api-v2/migration-guide#zoom-credentials-and-aan-attribution) for details.
</Callout>

## Using Credentials with Bots

### By Credential ID (Recommended)

Reference the stored credential directly:

```json
{
  "bot_name": "Recording Bot",
  "meeting_url": "https://zoom.us/j/123456789",
  "zoom_config": {
    "credential_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901"
  }
}
```

### By Zoom User ID

Look up a credential by the Zoom user ID:

```json
{
  "bot_name": "Recording Bot",
  "meeting_url": "https://zoom.us/j/123456789",
  "zoom_config": {
    "credential_user_id": "SeJwoMGwTCu52501SbDC0Q"
  }
}
```

This is useful when you store the Zoom user ID in your database and want to find the matching credential automatically.

## Listing Credentials

Get all credentials for your team:

<Tabs items={['cURL', 'Python', 'JavaScript']}>
  <Tab value="cURL">
    ```bash
    curl "https://api.meetingbaas.com/v2/zoom-credentials" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY"
    ```
  </Tab>
  <Tab value="Python">
    ```python
    import requests

    response = requests.get(
        "https://api.meetingbaas.com/v2/zoom-credentials",
        headers={"x-meeting-baas-api-key": "YOUR-API-KEY"}
    )
    for cred in response.json()["data"]:
        print(f"{cred['name']}: {cred['credential_type']} ({cred['state']})")
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript
    const response = await fetch("https://api.meetingbaas.com/v2/zoom-credentials", {
      headers: { "x-meeting-baas-api-key": "YOUR-API-KEY" },
    });
    const { data } = await response.json();
    data.forEach(cred => {
      console.log(`${cred.name}: ${cred.credential_type} (${cred.state})`);
    });
    ```
  </Tab>
</Tabs>

**Response:**

```json
{
  "success": true,
  "data": [
    {
      "credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "name": "Production Zoom App",
      "credential_type": "app",
      "zoom_user_id": null,
      "zoom_email": null,
      "zoom_display_name": null,
      "state": "active",
      "extra": null,
      ...
    },
    {
      "credential_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "name": "John Doe - Acme Corp",
      "credential_type": "user",
      "zoom_user_id": "SeJwoMGwTCu52501SbDC0Q",
      "zoom_email": "john.doe@acme.com",
      "zoom_display_name": "John Doe",
      "state": "active",
      "extra": { "internal_user_id": "u_42", "environment": "production" },
      ...
    }
  ]
}
```

### Filtering

The list endpoint accepts the following optional query parameters. All filters combine with AND.

| Parameter | Behaviour |
|-----------|-----------|
| `name` | Case-insensitive partial match on `name` |
| `zoom_email` | Case-insensitive partial match on `zoom_email` |
| `zoom_display_name` | Case-insensitive partial match on `zoom_display_name` |
| `zoom_user_id` | Exact match on `zoom_user_id` |
| `credential_type` | Comma-separated list (`app`, `user`) |
| `state` | Comma-separated list (`active`, `invalid`) |
| `extra` | Match values in the `extra` JSON payload using `key:value` syntax. Multiple conditions are comma-separated and must all match. Values are matched exactly (case-sensitive); credentials missing the key are excluded. |

Examples:

```bash
# Find a specific user's credentials by email substring
curl "https://api.meetingbaas.com/v2/zoom-credentials?zoom_email=john.doe" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"

# Only invalid user-type credentials
curl "https://api.meetingbaas.com/v2/zoom-credentials?credential_type=user&state=invalid" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"

# Filter by your own metadata stored in `extra`
curl "https://api.meetingbaas.com/v2/zoom-credentials?extra=internal_user_id:u_42,environment:production" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"
```

## Getting a Single Credential

Retrieve details for a specific credential:

```bash
curl "https://api.meetingbaas.com/v2/zoom-credentials/b2c3d4e5-f6a7-8901-bcde-f12345678901" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"
```

## Updating Credentials

Update an existing credential's name, SDK credentials, or re-authorize with new OAuth tokens.

### Update Name

```bash
curl -X PATCH "https://api.meetingbaas.com/v2/zoom-credentials/b2c3d4e5-f6a7-8901-bcde-f12345678901" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "name": "Jane Doe - Acme Corp"
         }'
```

### Update SDK Credentials

Update both client ID and secret together:

```bash
curl -X PATCH "https://api.meetingbaas.com/v2/zoom-credentials/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "client_id": "NEW_ZOOM_CLIENT_ID",
           "client_secret": "NEW_ZOOM_CLIENT_SECRET"
         }'
```

### Re-authorize with New OAuth Tokens

If a credential becomes invalid (user revoked access, tokens expired), you can re-authorize by providing a new authorization code. This resets the credential state to "active" and clears any error messages:

```bash
curl -X PATCH "https://api.meetingbaas.com/v2/zoom-credentials/b2c3d4e5-f6a7-8901-bcde-f12345678901" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "client_id": "YOUR_ZOOM_CLIENT_ID",
           "client_secret": "YOUR_ZOOM_CLIENT_SECRET",
           "authorization_code": "NEW_AUTHORIZATION_CODE",
           "redirect_uri": "https://your-app.com/oauth/callback"
         }'
```

<Callout>
Re-authorizing is useful when a credential becomes invalid. Instead of deleting and recreating, update the existing credential to preserve the same `credential_id` in your system.
</Callout>

## Deleting Credentials

Remove a credential and its stored tokens:

```bash
curl -X DELETE "https://api.meetingbaas.com/v2/zoom-credentials/b2c3d4e5-f6a7-8901-bcde-f12345678901" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"
```

<Callout type="warn">
Deleting a credential removes all stored tokens. Bots using this credential will fail to join meetings. The Zoom user would need to re-authorize your app to create a new credential.
</Callout>

## Credential States

### Active

The credential is working and can be used to join meetings.

### Invalid

The credential has failed. Common reasons:

- User revoked app access in Zoom settings
- OAuth token refresh failed
- Zoom account was deactivated
- Required scopes were removed from the app

When a credential becomes invalid:

1. Check `last_error_message` for details
2. Prompt the user to re-authorize your app
3. Update the existing credential with the new authorization code using `PATCH /v2/zoom-credentials/{id}` (this preserves the credential ID and resets the state to "active")

**Example invalid credential:**

```json
{
  "credential_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "name": "John Doe - Acme Corp",
  "credential_type": "user",
  "state": "invalid",
  "last_error_message": "Token refresh failed: invalid_grant",
  "last_error_at": "2026-02-09T15:30:00Z",
  ...
}
```

## Security

### Encryption

All sensitive data is encrypted at rest:

- **Client secrets**: AES-256-GCM encrypted
- **Access tokens**: AES-256-GCM encrypted
- **Refresh tokens**: AES-256-GCM encrypted

### What's Never Returned

API responses never include:

- Client secrets
- Access tokens
- Refresh tokens
- Encryption keys

You only receive metadata (IDs, names, states, timestamps).

### Access Control

Credentials are scoped to your team (API key). One team cannot access another team's credentials.

## Error Handling

### Creation Errors

| Status | Meaning |
|--------|---------|
| `400 Bad Request` | Missing required fields or invalid input |
| `400 Bad Request` | `redirect_uri` missing when `authorization_code` provided |
| `400 Bad Request` | Authorization code exchange failed (invalid code or URI mismatch) |

### Common OAuth Exchange Failures

**"invalid_grant"**: The authorization code has expired (valid for ~10 minutes) or was already used. Start a new OAuth flow.

**"redirect_uri_mismatch"**: The `redirect_uri` doesn't match what was used in the authorization URL. Ensure exact match including trailing slashes.

**"invalid_client"**: The client ID or secret is incorrect. Verify your Zoom app credentials.

## Best Practices

### Naming Conventions

Use descriptive names that help you identify credentials:

- **App credentials**: Include environment (e.g., "Production Zoom App", "Staging Bot")
- **User credentials**: Include user identifier (e.g., "John Doe - Acme Corp", "user@company.com")

### Monitoring Credential Health

Periodically check for invalid credentials:

```python
import requests

response = requests.get(
    "https://api.meetingbaas.com/v2/zoom-credentials",
    headers={"x-meeting-baas-api-key": "YOUR-API-KEY"}
)

for cred in response.json()["data"]:
    if cred["state"] == "invalid":
        print(f"Invalid credential: {cred['name']}")
        print(f"  Error: {cred['last_error_message']}")
        print(f"  Since: {cred['last_error_at']}")
        # Notify user to re-authorize
```

### Handle Revocations

Users can revoke your app's access in their Zoom settings. When this happens:

1. The credential state becomes `invalid`
2. Bots using this credential will fail
3. Prompt the user to re-authorize
4. Create a new credential and delete the old one

## FAQ

<Accordions type="single">

<Accordion title="How many credentials can I store?">
There's no hard limit. Store as many as you need for your users.
</Accordion>

<Accordion title="What can I update on an existing credential?">
You can update the name, SDK credentials (client ID and secret together), or re-authorize with new OAuth tokens using `PATCH /v2/zoom-credentials/{id}`. Re-authorizing is useful when a credential becomes invalid.
</Accordion>

<Accordion title="What happens to bots when a credential becomes invalid?">
Bots created with that credential will fail to join with a `ZOOM_ACCESS_TOKEN_ERROR` or similar error. Already-running bots are not affected.
</Accordion>

<Accordion title="How long are OAuth tokens valid?">
Zoom access tokens expire after 1 hour. Meeting BaaS automatically refreshes them using the refresh token. If refresh fails, the credential becomes invalid.
</Accordion>

<Accordion title="Do I need separate credentials for SDK and OBF?">
For internal meetings: App-only credentials are sufficient.
For external meetings: You need user credentials (with OAuth) for OBF token support.
</Accordion>

</Accordions>

## Next Steps

- [OBF Token Support](/docs/api-v2/authenticated-bots/zoom/obf-tokens) — Use credentials for external meetings
- [OAuth Consent Flow](/docs/api-v2/authenticated-bots/zoom/oauth-consent-flow) — Build the user authorization flow
- [Zoom App Setup](/docs/api/getting-started/zoom/app-setup) — Create or configure your Zoom app


---

## Zoom Integration

Configure Zoom bots with SDK credentials, OBF tokens, and secure credential management in v2

### Source: ./content/docs/api-v2/authenticated-bots/zoom/index.mdx


# Zoom Integration

Meeting BaaS v2 provides enhanced Zoom integration with secure credential management, improved OBF token handling, and better error tracking.

<Callout type="warn">
**March 2, 2026 Deadline:** Zoom requires OBF tokens for bots joining external meetings. If your bots join meetings hosted by external Zoom accounts, you must implement OBF tokens before this date. See [OBF Token Support](/docs/api-v2/authenticated-bots/zoom/obf-tokens) for details and [Zoom's official announcement](https://developers.zoom.us/blog/transition-to-obf-token-meetingsdk-apps/).
</Callout>

## What's New in v2

The v2 API introduces several improvements for Zoom integration:

| Feature | v1 | v2 |
|---------|----|----|
| **Credential Storage** | Separate OAuth connection endpoint | Unified `/v2/zoom-credentials` API |
| **SDK Credentials** | Passed with each bot request | Store once, reference by ID |
| **Encryption** | Basic | AES-256-GCM for secrets and tokens |
| **Credential Types** | OAuth only | App-only (SDK) and User (OAuth) |
| **State Tracking** | Limited | Active/Invalid with error tracking |
| **Configuration** | Multiple top-level params | Single `zoom_config` object |

## Two Approaches

How you integrate with Zoom depends on whose meetings your bots join:

<Cards>
  <Card title="Internal Meetings" href="/docs/api-v2/authenticated-bots/zoom/credentials#app-only-credentials">
    Bots join meetings within your Zoom organization. Store SDK credentials once and reference them in bot requests.
  </Card>
  <Card title="External Meetings" href="/docs/api-v2/authenticated-bots/zoom/obf-tokens">
    Bots join meetings hosted by external accounts. OBF tokens required after March 2, 2026.
  </Card>
</Cards>

## Quick Decision Guide

| Your Use Case | What You Need | Documentation |
|---------------|---------------|---------------|
| Recording your team's meetings | App-only credentials | [Zoom Credentials](/docs/api-v2/authenticated-bots/zoom/credentials) |
| Building a product for customers | User credentials with OBF | [OBF Token Support](/docs/api-v2/authenticated-bots/zoom/obf-tokens) |
| Joining meetings hosted by others | OBF tokens | [OBF Token Support](/docs/api-v2/authenticated-bots/zoom/obf-tokens) |
| Already managing OAuth yourself | Direct token or Token URL | [OBF Token Support](/docs/api-v2/authenticated-bots/zoom/obf-tokens) |

## The `zoom_config` Object

In v2, all Zoom-specific configuration is passed in a single `zoom_config` object:

```json
{
  "bot_name": "Recording Bot",
  "meeting_url": "https://zoom.us/j/123456789",
  "zoom_config": {
    "credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}
```

Available options within `zoom_config`:

| Parameter | Description |
|-----------|-------------|
| `credential_id` | UUID of a stored credential (recommended for most use cases) |
| `credential_user_id` | Look up stored credential by Zoom user ID |
| `obf_token` | Direct OBF token for one-off joins |
| `obf_token_url` | URL that returns an OBF token at join time |
| `zak_token_url` | URL that returns a ZAK token for host-level access |

## Pages in This Section

<Cards>
  <Card title="Zoom Credentials" href="/docs/api-v2/authenticated-bots/zoom/credentials">
    Store and manage Zoom app credentials and OAuth tokens with the credentials API.
  </Card>
  <Card title="OBF Token Support" href="/docs/api-v2/authenticated-bots/zoom/obf-tokens">
    Implement OBF tokens for external meetings. Covers all integration options.
  </Card>
  <Card title="OAuth Consent Flow" href="/docs/api-v2/authenticated-bots/zoom/oauth-consent-flow">
    Build an OAuth consent flow for your users to authorize your Zoom app.
  </Card>
</Cards>

## Key Concepts

### Credential Types

**App-only credentials** store your Zoom app's SDK credentials (client ID and secret). Use these when your bots only join internal meetings.

**User credentials** store OAuth tokens for a specific Zoom user who authorized your app. These enable OBF token support for joining external meetings.

### Credential States

v2 tracks credential health:

- **active**: Credential is working and can be used
- **invalid**: Credential has failed (e.g., tokens revoked, refresh failed)

When a credential becomes invalid, `last_error_message` and `last_error_at` provide debugging information.

### Security

All credentials are encrypted at rest using AES-256-GCM. Client secrets and OAuth tokens are never returned in API responses—only credential IDs and metadata.

## FAQ

<Accordions type="single">

<Accordion title="Can I migrate my v1 Zoom OAuth connections to v2?">
Yes, but you'll need to create new credentials in v2 using the `/v2/zoom-credentials` endpoint. We recommend having users re-authorize to ensure fresh tokens.
</Accordion>

<Accordion title="Do I need to change my Zoom app configuration?">
No, your existing Zoom app works with v2. The scopes and settings remain the same.
</Accordion>

<Accordion title="What happens if a credential becomes invalid?">
Bots using that credential will fail to join meetings. You'll see the error in the credential's `last_error_message` field and in the bot's failure webhook.
</Accordion>

<Accordion title="Can I use both v1 and v2 APIs simultaneously?">
Yes, during migration you can use both APIs. However, credentials are not shared between v1 and v2—you'll need to set them up separately.
</Accordion>

</Accordions>

## Related Resources

- [Zoom App Setup](/docs/api/getting-started/zoom/app-setup) — Create a Zoom app in the Marketplace (same for v1 and v2)
- [Sending a Bot](/docs/api-v2/getting-started/sending-a-bot) — Basic bot creation in v2
- [Zoom's OBF Blog Post](https://developers.zoom.us/blog/transition-to-obf-token-meetingsdk-apps/) — Official announcement
- [Zoom's OBF FAQ](https://developers.zoom.us/docs/meeting-sdk/obf-faq/) — Detailed Q&A from Zoom


---

## Building OAuth Consent Flow

Step-by-step guide for implementing Zoom OAuth consent in your application for v2

### Source: ./content/docs/api-v2/authenticated-bots/zoom/oauth-consent-flow.mdx


# Building OAuth Consent Flow

To use OBF tokens with the v2 Credentials API, your users need to authorize your Zoom app. This guide walks through implementing the OAuth consent flow in your application.

## Overview

The OAuth flow has three steps:

```
User clicks "Connect Zoom" → Redirected to Zoom → Authorizes → Redirected back with code → You exchange code for credential
```

In v2, you exchange the authorization code directly via the Credentials API, which handles token exchange and secure storage.

## Prerequisites

Before implementing the OAuth flow:

1. Create a Zoom app with OAuth enabled (see [Zoom App Setup](/docs/api/getting-started/zoom/app-setup))
2. Add the required scopes: `user:read:token`, `user:read:user`, `user:read:zak`
3. Configure a redirect URI in your Zoom app settings
4. Have your Zoom Client ID and Client Secret ready

## Step 1: Build the Authorization URL

Create a link that sends users to Zoom's authorization page:

```
https://zoom.us/oauth/authorize?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}
```

### Required Parameters

| Parameter | Description |
|-----------|-------------|
| `response_type` | Always `code` |
| `client_id` | Your Zoom app's Client ID |
| `redirect_uri` | Where Zoom sends the user after authorization (must match your app settings exactly) |

### Optional Parameters

| Parameter | Description |
|-----------|-------------|
| `state` | Random string to prevent CSRF attacks. Verify this matches when the user returns. |

### Example Implementation

<Tabs items={['React', 'Next.js', 'Node.js']}>
  <Tab value="React">
    ```jsx title="ConnectZoomButton.jsx"
    function ConnectZoomButton() {
      const handleConnect = () => {
        const params = new URLSearchParams({
          response_type: "code",
          client_id: process.env.REACT_APP_ZOOM_CLIENT_ID,
          redirect_uri: `${window.location.origin}/oauth/zoom/callback`,
          state: crypto.randomUUID(), // Store this to verify later
        });

        window.location.href = `https://zoom.us/oauth/authorize?${params}`;
      };

      return (
        <button onClick={handleConnect}>
          Connect Zoom Account
        </button>
      );
    }
    ```
  </Tab>
  <Tab value="Next.js">
    ```tsx title="app/connect-zoom/page.tsx"
    import { redirect } from "next/navigation";
    import { cookies } from "next/headers";

    export default function ConnectZoomPage() {
      async function connectZoom() {
        "use server";

        const state = crypto.randomUUID();

        // Store state in cookie for verification
        cookies().set("zoom_oauth_state", state, {
          httpOnly: true,
          secure: true,
          sameSite: "lax",
          maxAge: 600, // 10 minutes
        });

        const params = new URLSearchParams({
          response_type: "code",
          client_id: process.env.ZOOM_CLIENT_ID!,
          redirect_uri: `${process.env.NEXT_PUBLIC_APP_URL}/oauth/zoom/callback`,
          state,
        });

        redirect(`https://zoom.us/oauth/authorize?${params}`);
      }

      return (
        <form action={connectZoom}>
          <button type="submit">Connect Zoom Account</button>
        </form>
      );
    }
    ```
  </Tab>
  <Tab value="Node.js">
    ```javascript title="routes/zoom.js"
    const express = require("express");
    const crypto = require("crypto");
    const router = express.Router();

    router.get("/connect", (req, res) => {
      const state = crypto.randomUUID();

      // Store state in session for verification
      req.session.zoomOAuthState = state;

      const params = new URLSearchParams({
        response_type: "code",
        client_id: process.env.ZOOM_CLIENT_ID,
        redirect_uri: `${process.env.APP_URL}/oauth/zoom/callback`,
        state,
      });

      res.redirect(`https://zoom.us/oauth/authorize?${params}`);
    });

    module.exports = router;
    ```
  </Tab>
</Tabs>

## Step 2: Handle the Callback

After the user authorizes (or denies), Zoom redirects to your redirect URI with query parameters:

**Success:**
```
https://your-app.com/oauth/zoom/callback?code=AUTHORIZATION_CODE&state=YOUR_STATE
```

**User Denied:**
```
https://your-app.com/oauth/zoom/callback?error=access_denied
```

### Callback Handler

<Tabs items={['Next.js', 'Express', 'Python']}>
  <Tab value="Next.js">
    ```tsx title="app/oauth/zoom/callback/route.ts"
    import { cookies } from "next/headers";
    import { redirect } from "next/navigation";
    import { NextRequest } from "next/server";

    export async function GET(request: NextRequest) {
      const searchParams = request.nextUrl.searchParams;
      const code = searchParams.get("code");
      const state = searchParams.get("state");
      const error = searchParams.get("error");

      // Handle user denial
      if (error) {
        redirect("/settings?error=zoom_denied");
      }

      // Verify state to prevent CSRF
      const storedState = cookies().get("zoom_oauth_state")?.value;
      if (state !== storedState) {
        redirect("/settings?error=invalid_state");
      }

      // Clear the state cookie
      cookies().delete("zoom_oauth_state");

      // Exchange code for credential via Meeting BaaS
      const response = await fetch("https://api.meetingbaas.com/v2/zoom-credentials", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "x-meeting-baas-api-key": process.env.MEETING_BAAS_API_KEY!,
        },
        body: JSON.stringify({
          name: `User ${userId}`, // Get from your session
          client_id: process.env.ZOOM_CLIENT_ID,
          client_secret: process.env.ZOOM_CLIENT_SECRET,
          authorization_code: code,
          redirect_uri: `${process.env.NEXT_PUBLIC_APP_URL}/oauth/zoom/callback`,
        }),
      });

      if (!response.ok) {
        console.error("Failed to create credential:", await response.text());
        redirect("/settings?error=zoom_exchange_failed");
      }

      const { data } = await response.json();

      // Store the credential_id, zoom_user_id, and zoom_email in your database
      await saveZoomCredential(userId, {
        credentialId: data.credential_id,
        zoomUserId: data.zoom_user_id,
        zoomEmail: data.zoom_email, // shows the user which Zoom account is connected
      });

      redirect("/settings?success=zoom_connected");
    }
    ```
  </Tab>
  <Tab value="Express">
    ```javascript title="routes/zoom.js"
    router.get("/callback", async (req, res) => {
      const { code, state, error } = req.query;

      // Handle user denial
      if (error) {
        return res.redirect("/settings?error=zoom_denied");
      }

      // Verify state
      if (state !== req.session.zoomOAuthState) {
        return res.redirect("/settings?error=invalid_state");
      }

      delete req.session.zoomOAuthState;

      try {
        // Exchange code for credential via Meeting BaaS
        const response = await fetch("https://api.meetingbaas.com/v2/zoom-credentials", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "x-meeting-baas-api-key": process.env.MEETING_BAAS_API_KEY,
          },
          body: JSON.stringify({
            name: `User ${req.user.id}`,
            client_id: process.env.ZOOM_CLIENT_ID,
            client_secret: process.env.ZOOM_CLIENT_SECRET,
            authorization_code: code,
            redirect_uri: `${process.env.APP_URL}/oauth/zoom/callback`,
          }),
        });

        if (!response.ok) {
          throw new Error(await response.text());
        }

        const { data } = await response.json();

        // Save to your database
        await db.user.update({
          where: { id: req.user.id },
          data: {
            zoomCredentialId: data.credential_id,
            zoomUserId: data.zoom_user_id,
            zoomEmail: data.zoom_email,
          },
        });

        res.redirect("/settings?success=zoom_connected");
      } catch (err) {
        console.error("Zoom OAuth error:", err);
        res.redirect("/settings?error=zoom_exchange_failed");
      }
    });
    ```
  </Tab>
  <Tab value="Python">
    ```python title="routes/zoom.py"
    from flask import Flask, request, redirect, session
    import requests

    @app.route("/oauth/zoom/callback")
    def zoom_callback():
        code = request.args.get("code")
        state = request.args.get("state")
        error = request.args.get("error")

        # Handle user denial
        if error:
            return redirect("/settings?error=zoom_denied")

        # Verify state
        if state != session.get("zoom_oauth_state"):
            return redirect("/settings?error=invalid_state")

        del session["zoom_oauth_state"]

        # Exchange code for credential via Meeting BaaS
        response = requests.post(
            "https://api.meetingbaas.com/v2/zoom-credentials",
            headers={
                "Content-Type": "application/json",
                "x-meeting-baas-api-key": os.environ["MEETING_BAAS_API_KEY"],
            },
            json={
                "name": f"User {current_user.id}",
                "client_id": os.environ["ZOOM_CLIENT_ID"],
                "client_secret": os.environ["ZOOM_CLIENT_SECRET"],
                "authorization_code": code,
                "redirect_uri": f"{os.environ['APP_URL']}/oauth/zoom/callback",
            },
        )

        if not response.ok:
            print(f"Zoom OAuth error: {response.text}")
            return redirect("/settings?error=zoom_exchange_failed")

        data = response.json()["data"]

        # Save to your database
        current_user.zoom_credential_id = data["credential_id"]
        current_user.zoom_user_id = data["zoom_user_id"]
        current_user.zoom_email = data["zoom_email"]
        db.session.commit()

        return redirect("/settings?success=zoom_connected")
    ```
  </Tab>
</Tabs>

## Step 3: Use the Credential

Once saved, use the `credential_id` when creating bots:

```python
response = requests.post(
    "https://api.meetingbaas.com/v2/bots",
    headers={
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
    },
    json={
        "bot_name": "Recording Bot",
        "meeting_url": meeting_url,
        "zoom_config": {
            "credential_id": user.zoom_credential_id
        }
    }
)
```

## Complete Example: Express.js

Here's a full working example:

```javascript title="server.js"
const express = require("express");
const session = require("express-session");
const crypto = require("crypto");

const app = express();

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
}));

// Start OAuth flow
app.get("/connect-zoom", (req, res) => {
  if (!req.user) {
    return res.redirect("/login");
  }

  const state = crypto.randomUUID();
  req.session.zoomOAuthState = state;

  const params = new URLSearchParams({
    response_type: "code",
    client_id: process.env.ZOOM_CLIENT_ID,
    redirect_uri: `${process.env.APP_URL}/oauth/zoom/callback`,
    state,
  });

  res.redirect(`https://zoom.us/oauth/authorize?${params}`);
});

// Handle callback
app.get("/oauth/zoom/callback", async (req, res) => {
  const { code, state, error } = req.query;

  if (error) {
    return res.redirect("/settings?error=zoom_denied");
  }

  if (!req.session.zoomOAuthState || state !== req.session.zoomOAuthState) {
    return res.redirect("/settings?error=invalid_state");
  }

  delete req.session.zoomOAuthState;

  try {
    const response = await fetch("https://api.meetingbaas.com/v2/zoom-credentials", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": process.env.MEETING_BAAS_API_KEY,
      },
      body: JSON.stringify({
        name: `${req.user.email} - Zoom`,
        client_id: process.env.ZOOM_CLIENT_ID,
        client_secret: process.env.ZOOM_CLIENT_SECRET,
        authorization_code: code,
        redirect_uri: `${process.env.APP_URL}/oauth/zoom/callback`,
      }),
    });

    if (!response.ok) {
      const errorText = await response.text();
      console.error("Meeting BaaS error:", errorText);
      return res.redirect("/settings?error=exchange_failed");
    }

    const { data } = await response.json();

    // Save credential info to your database
    await updateUser(req.user.id, {
      zoomCredentialId: data.credential_id,
      zoomUserId: data.zoom_user_id,
      zoomEmail: data.zoom_email,
      zoomConnected: true,
    });

    res.redirect("/settings?success=zoom_connected");
  } catch (err) {
    console.error("OAuth error:", err);
    res.redirect("/settings?error=unknown");
  }
});

// Disconnect Zoom
app.post("/disconnect-zoom", async (req, res) => {
  if (!req.user?.zoomCredentialId) {
    return res.redirect("/settings");
  }

  try {
    await fetch(
      `https://api.meetingbaas.com/v2/zoom-credentials/${req.user.zoomCredentialId}`,
      {
        method: "DELETE",
        headers: {
          "x-meeting-baas-api-key": process.env.MEETING_BAAS_API_KEY,
        },
      }
    );

    await updateUser(req.user.id, {
      zoomCredentialId: null,
      zoomUserId: null,
      zoomConnected: false,
    });

    res.redirect("/settings?success=zoom_disconnected");
  } catch (err) {
    console.error("Disconnect error:", err);
    res.redirect("/settings?error=disconnect_failed");
  }
});

app.listen(3000);
```

## UI Recommendations

### Connect Button States

Show different UI based on connection status:

```jsx
function ZoomConnection({ user }) {
  if (user.zoomConnected) {
    return (
      <div className="connection-card connected">
        <ZoomIcon />
        <div>
          <h3>Zoom Connected</h3>
          <p>Connected as {user.zoomEmail}</p>
        </div>
        <form action="/disconnect-zoom" method="POST">
          <button type="submit" className="btn-secondary">
            Disconnect
          </button>
        </form>
      </div>
    );
  }

  return (
    <div className="connection-card">
      <ZoomIcon />
      <div>
        <h3>Connect Zoom</h3>
        <p>Allow recording bots to join your Zoom meetings</p>
      </div>
      <a href="/connect-zoom" className="btn-primary">
        Connect
      </a>
    </div>
  );
}
```

### Error Messages

Show user-friendly messages for common errors:

| Error | User Message |
|-------|--------------|
| `zoom_denied` | "You declined to connect your Zoom account. You can try again anytime." |
| `invalid_state` | "Something went wrong. Please try connecting again." |
| `exchange_failed` | "We couldn't complete the connection. Please try again or contact support." |

### Credential Health

Monitor credential state and prompt re-authorization when needed:

```jsx
function ZoomConnectionStatus({ credential }) {
  if (credential.state === "invalid") {
    return (
      <div className="alert warning">
        <p>
          Your Zoom connection needs to be refreshed.
          <a href="/connect-zoom">Reconnect</a>
        </p>
        <small>Error: {credential.lastErrorMessage}</small>
      </div>
    );
  }

  return <p className="text-success">Connected and working</p>;
}
```

## Security Best Practices

### State Parameter

Always use the `state` parameter to prevent CSRF attacks:

1. Generate a random string before redirecting to Zoom
2. Store it in the session (server-side)
3. Verify it matches when the user returns
4. Reject the callback if it doesn't match

### Secure Cookie Settings

When storing state in cookies:

```javascript
cookies().set("zoom_oauth_state", state, {
  httpOnly: true,     // Not accessible via JavaScript
  secure: true,       // HTTPS only
  sameSite: "lax",    // CSRF protection
  maxAge: 600,        // 10 minute expiry
});
```

### Redirect URI Validation

The `redirect_uri` must exactly match what's registered in your Zoom app, including:
- Protocol (https://)
- Domain
- Port (if non-standard)
- Path
- Trailing slash (or lack thereof)

## Error Handling

### Authorization Code Expired

Authorization codes are valid for approximately 10 minutes. If the exchange fails with "invalid_grant", the code has expired. Prompt the user to try again.

### Redirect URI Mismatch

If you see "redirect_uri_mismatch", verify:
1. The URI in your code matches your Zoom app settings
2. No trailing slash differences
3. No http vs https differences

### User Revoked Access

If a user revokes your app in their Zoom settings, the credential becomes invalid. Handle this gracefully:

1. Check credential state before creating bots
2. If invalid, prompt re-authorization
3. Delete the old credential after successful re-auth

## FAQ

<Accordions type="single">

<Accordion title="Can I use the same redirect URI for development and production?">
No, use environment-specific URIs. Add both to your Zoom app's allowed redirect URIs.
</Accordion>

<Accordion title="What if the user has multiple Zoom accounts?">
They'll authorize with whichever account they're logged into. The credential response includes `zoom_email` and `zoom_display_name` (captured from Zoom's `/users/me` API at OAuth time, requires the `user:read:user` scope) — surface these in your UI so the user can verify which Zoom account is connected and disconnect/reconnect if it's the wrong one.
</Accordion>

<Accordion title="How do I handle re-authorization?">
When creating a new credential for an existing Zoom user, the old credential becomes orphaned. Delete it after successful re-auth to avoid confusion.
</Accordion>

<Accordion title="Can I customize what users see on Zoom's authorization page?">
Limited customization is available in your Zoom app settings (app name, icon, description).
</Accordion>

</Accordions>

## Next Steps

- [Zoom Credentials API](/docs/api-v2/authenticated-bots/zoom/credentials) — Manage stored credentials
- [OBF Token Support](/docs/api-v2/authenticated-bots/zoom/obf-tokens) — Use credentials for external meetings
- [Sending a Bot](/docs/api-v2/getting-started/sending-a-bot) — Create bots with Zoom credentials


---

## OBF Token Support

Configure OBF (On Behalf Of) tokens for Zoom bots joining external meetings in v2

### Source: ./content/docs/api-v2/authenticated-bots/zoom/obf-tokens.mdx


# Zoom OBF Token Support

Starting **March 2, 2026**, Zoom requires Meeting SDK applications to use On Behalf Of (OBF) tokens when joining meetings they did not create. This page explains what OBF tokens are, who needs them, and how to implement them with the v2 API.

<Callout type="warn">
**Deadline:** March 2, 2026. After this date, bots joining external Zoom meetings without OBF tokens will fail to join. See [Zoom's official announcement](https://developers.zoom.us/blog/transition-to-obf-token-meetingsdk-apps/).
</Callout>

## What is an OBF Token?

An OBF (On Behalf Of) token is a Zoom authorization token that proves a specific Zoom user has authorized your bot to join meetings on their behalf.

**Key characteristics:**

- **User-specific**: Each token is tied to a particular Zoom user who authorized your app
- **Short-lived**: Tokens should be fetched close to when they are needed
- **Meeting SDK only**: Zoom web SDK, iOS SDK, Android SDK, Windows SDK, Linux SDK—all require OBF
- **Authorized user presence**: The user who authorized the token must be present in the meeting

<Callout>
**Authorized User Presence:** When using OBF tokens, the Zoom user who authorized your app must be in the meeting. If they leave, the bot is disconnected. This is a Zoom platform requirement.
</Callout>

## Who Needs OBF Tokens?

### You NEED OBF tokens if:

- Your bots join Zoom meetings created by people **outside** your Zoom organization
- You're building a product where customers request meeting recordings
- You use Meeting BaaS as infrastructure for a service where end users have their own Zoom accounts
- Any scenario where the meeting host is not part of your Zoom account

### You do NOT need OBF tokens if:

- Your bots only join meetings **within** your own Zoom account/organization
- You use your own [SDK credentials](/docs/api-v2/authenticated-bots/zoom/credentials#app-only-credentials) (makes all meetings "internal")
- You only use Google Meet or Microsoft Teams bots

## Four Integration Options

The v2 API supports four ways to provide OBF tokens via the `zoom_config` object:

| Option | Parameter | Best For |
|--------|-----------|----------|
| **Stored Credential** | `credential_id` | Most users—set up once, fully managed |
| **User ID Lookup** | `credential_user_id` | When you store Zoom user IDs in your system |
| **Direct Token** | `obf_token` | Testing, or existing OAuth infrastructure |
| **Token URL** | `obf_token_url` | Keep credentials on your infrastructure |

---

## Option 1: Stored Credential (Recommended)

Store the user's OAuth tokens via the [Credentials API](/docs/api-v2/authenticated-bots/zoom/credentials), then reference the credential when creating bots.

**How it works:**
1. User completes OAuth consent flow → you receive authorization code
2. Create a credential with the authorization code → Meeting BaaS stores encrypted tokens
3. When creating bots, pass `credential_id` → we fetch fresh OBF token automatically

### Step 1: Create OAuth Credential

After the user authorizes your app:

```bash
curl -X POST "https://api.meetingbaas.com/v2/zoom-credentials" \
     -H "Content-Type: application/json" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY" \
     -d '{
           "name": "John Doe - Acme Corp",
           "client_id": "YOUR_ZOOM_CLIENT_ID",
           "client_secret": "YOUR_ZOOM_CLIENT_SECRET",
           "authorization_code": "AUTHORIZATION_CODE_FROM_ZOOM",
           "redirect_uri": "https://your-app.com/oauth/callback"
         }'
```

**Response:**

```json
{
  "success": true,
  "data": {
    "credential_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
    "zoom_user_id": "SeJwoMGwTCu52501SbDC0Q",
    "credential_type": "user",
    "state": "active",
    ...
  }
}
```

### Step 2: Create Bots with Credential

<Tabs items={['cURL', 'Python', 'JavaScript']}>
  <Tab value="cURL">
    ```bash
    curl -X POST "https://api.meetingbaas.com/v2/bots" \
         -H "Content-Type: application/json" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY" \
         -d '{
               "bot_name": "Recording Bot",
               "meeting_url": "https://zoom.us/j/123456789",
               "zoom_config": {
                 "credential_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901"
               }
             }'
    ```
  </Tab>
  <Tab value="Python">
    ```python
    import requests

    response = requests.post(
        "https://api.meetingbaas.com/v2/bots",
        headers={
            "Content-Type": "application/json",
            "x-meeting-baas-api-key": "YOUR-API-KEY",
        },
        json={
            "bot_name": "Recording Bot",
            "meeting_url": "https://zoom.us/j/123456789",
            "zoom_config": {
                "credential_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901"
            }
        }
    )
    print(response.json())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript
    const response = await fetch("https://api.meetingbaas.com/v2/bots", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
      },
      body: JSON.stringify({
        bot_name: "Recording Bot",
        meeting_url: "https://zoom.us/j/123456789",
        zoom_config: {
          credential_id: "b2c3d4e5-f6a7-8901-bcde-f12345678901",
        },
      }),
    });
    console.log(await response.json());
    ```
  </Tab>
</Tabs>

**What happens:**
1. Bot is queued to join
2. At join time, we fetch a fresh OBF token using the stored OAuth tokens
3. Bot joins the meeting on behalf of the authorized user

**Pros:**
- Fully automated after initial setup
- Token always fresh (fetched at join time)
- Automatic token refresh handling
- Error tracking via credential state

**Cons:**
- Requires building OAuth consent flow for users
- OAuth credentials stored in Meeting BaaS

---

## Option 2: User ID Lookup

If you store Zoom user IDs in your database, you can look up credentials by user ID instead of credential ID.

```json
{
  "bot_name": "Recording Bot",
  "meeting_url": "https://zoom.us/j/123456789",
  "zoom_config": {
    "credential_user_id": "SeJwoMGwTCu52501SbDC0Q"
  }
}
```

Meeting BaaS finds the stored credential matching this Zoom user ID and uses it to fetch the OBF token.

**Pros:**
- Simpler integration if you already track Zoom user IDs
- No need to store credential UUIDs

**Cons:**
- Slightly slower (lookup required)
- Fails if multiple credentials exist for the same Zoom user ID

---

## Option 3: Direct Token

Fetch the OBF token yourself and pass it directly.

<Tabs items={['cURL', 'Python', 'JavaScript']}>
  <Tab value="cURL">
    ```bash
    # First, get the OBF token from Zoom
    OBF_TOKEN=$(curl -s "https://api.zoom.us/v2/users/me/token?type=onbehalf" \
         -H "Authorization: Bearer YOUR_USER_ACCESS_TOKEN" | jq -r .token)

    # Then, create bot with the token
    curl -X POST "https://api.meetingbaas.com/v2/bots" \
         -H "Content-Type: application/json" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY" \
         -d '{
               "bot_name": "Recording Bot",
               "meeting_url": "https://zoom.us/j/123456789",
               "zoom_config": {
                 "obf_token": "'"$OBF_TOKEN"'"
               }
             }'
    ```
  </Tab>
  <Tab value="Python">
    ```python
    import requests

    # First, fetch OBF token from Zoom
    zoom_response = requests.get(
        "https://api.zoom.us/v2/users/me/token?type=onbehalf",
        headers={"Authorization": f"Bearer {user_access_token}"}
    )
    obf_token = zoom_response.json()["token"]

    # Then, create bot with the token
    response = requests.post(
        "https://api.meetingbaas.com/v2/bots",
        headers={
            "Content-Type": "application/json",
            "x-meeting-baas-api-key": "YOUR-API-KEY",
        },
        json={
            "bot_name": "Recording Bot",
            "meeting_url": "https://zoom.us/j/123456789",
            "zoom_config": {
                "obf_token": obf_token
            }
        }
    )
    print(response.json())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript
    // First, fetch OBF token from Zoom
    const zoomResponse = await fetch(
      "https://api.zoom.us/v2/users/me/token?type=onbehalf",
      { headers: { Authorization: `Bearer ${userAccessToken}` } }
    );
    const { token: obfToken } = await zoomResponse.json();

    // Then, create bot with the token
    const response = await fetch("https://api.meetingbaas.com/v2/bots", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
      },
      body: JSON.stringify({
        bot_name: "Recording Bot",
        meeting_url: "https://zoom.us/j/123456789",
        zoom_config: {
          obf_token: obfToken,
        },
      }),
    });
    console.log(await response.json());
    ```
  </Tab>
</Tabs>

**Pros:**
- Full control over token lifecycle
- No credentials stored in Meeting BaaS

**Cons:**
- You manage OAuth token storage and refresh
- Token may expire if there's delay before bot joins
- Must fetch fresh token for each request

**Best for:** Testing, debugging, or when you already have Zoom OAuth infrastructure.

---

## Option 4: Token URL

Host an endpoint that returns OBF tokens. The bot calls your endpoint at join time.

```json
{
  "bot_name": "Recording Bot",
  "meeting_url": "https://zoom.us/j/123456789",
  "zoom_config": {
    "obf_token_url": "https://your-api.com/zoom/obf-token"
  }
}
```

### Parameters Passed to Your Endpoint

When the bot calls your endpoint, it appends query parameters:

| Parameter | Description |
|-----------|-------------|
| `bot_id` | The Meeting BaaS bot ID (UUID) |
| `extra` | URL-encoded JSON of the `extra` data from the bot request |

**Example request to your endpoint:**

```
GET https://your-api.com/zoom/obf-token?bot_id=abc-123-def&extra=%7B%22user_id%22%3A%22usr_456%22%7D
```

### Your Endpoint Requirements

- Accept GET requests
- Return the OBF token as plain text (ASCII, not JSON)
- Respond within 15 seconds (timeout)
- Handle OAuth token refresh internally

### Example Endpoint Implementation

```python title="your_endpoint.py"
from flask import Flask, request
import requests
import json

app = Flask(__name__)

@app.route("/zoom/obf-token")
def get_obf_token():
    # Parse identifiers from query params
    bot_id = request.args.get("bot_id")
    extra_str = request.args.get("extra", "{}")
    extra = json.loads(extra_str)
    user_id = extra.get("user_id")

    # Look up stored OAuth credentials for this user
    access_token = get_stored_access_token(user_id)

    # Refresh if expired
    if is_expired(access_token):
        access_token = refresh_access_token(user_id)

    # Fetch OBF token from Zoom
    response = requests.get(
        "https://api.zoom.us/v2/users/me/token?type=onbehalf",
        headers={"Authorization": f"Bearer {access_token}"}
    )

    # Return raw token (not JSON)
    return response.json()["token"]
```

**Pros:**
- Token always fresh (fetched at join time)
- Credentials stay on your infrastructure

**Cons:**
- You must host and maintain an endpoint
- You must implement OAuth token storage and refresh

---

## App Attribution (Active Apps Notifier)

When using OBF tokens, the bot's **SDK credentials** determine which app name appears in Zoom's [Active Apps Notifier (AAN)](https://developers.zoom.us/docs/meeting-sdk/ui-notices/#active-apps-notifier-aan-use-case) — the notice shown to all participants identifying which app is accessing meeting content.

### How Each Option Affects AAN

| Option | AAN Shows | Why |
|--------|-----------|-----|
| **Stored Credential** (`credential_id`) | **Your app name** | Your SDK credentials are stored in the credential |
| **User ID Lookup** (`credential_user_id`) | **Your app name** | Resolved credential includes your SDK credentials |
| **Direct Token** (`obf_token`) only | "Meeting Baas" | No SDK credentials provided — falls back to defaults |
| **Token URL** (`obf_token_url`) only | "Meeting Baas" | No SDK credentials provided — falls back to defaults |

### Fixing AAN for Direct Token or Token URL

If you use `obf_token` or `obf_token_url` and need the AAN to display your app name, combine it with a stored **app-only credential**:

```json
{
  "bot_name": "Recording Bot",
  "meeting_url": "https://zoom.us/j/123456789",
  "zoom_config": {
    "credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "obf_token_url": "https://your-api.com/zoom/obf-token"
  }
}
```

The stored credential provides your SDK keys for AAN attribution, while your endpoint continues handling OBF token generation. See [Zoom Credentials](/docs/api-v2/authenticated-bots/zoom/credentials#active-apps-notifier-aan-attribution) for how to create an app-only credential.

<Callout type="warn">
**Zoom Marketplace Requirement:** During review, Zoom requires the AAN to show your app name. If you're going through Marketplace review, the reviewer may flag this if your bot requests don't include a stored credential. See [Zoom's AAN documentation](https://developers.zoom.us/docs/meeting-sdk/ui-notices/#active-apps-notifier-aan-use-case).
</Callout>

## Bot Behavior with OBF Tokens

### Authorized User Not Yet in Meeting

When the bot joins with an OBF token and the authorized user hasn't joined yet:

1. Bot attempts to join
2. Zoom returns "authorized user not in meeting"
3. Bot retries every 30 seconds
4. Once the user joins, bot enters successfully
5. If user doesn't join within timeout, bot exits with error

Configure the timeout:

```json
{
  "bot_name": "Recording Bot",
  "meeting_url": "https://zoom.us/j/123456789",
  "zoom_config": {
    "credential_id": "..."
  },
  "timeout_config": {
    "waiting_room_timeout": 300
  }
}
```

### Authorized User Leaves Meeting

If the authorized user leaves while the bot is active:

1. Zoom terminates the SDK session
2. Bot stops recording
3. Bot uploads any recorded content
4. Completion webhook is sent
5. Bot exits

This is a Zoom requirement and cannot be changed.

## Error Codes

| Error Code | Meaning | Resolution |
|------------|---------|------------|
| `ZOOM_ACCESS_TOKEN_ERROR` | Failed to fetch or use OBF token | Check credential state, verify OAuth tokens |
| `SDK_AUTH_FAILED` | SDK authentication failed | Verify SDK credentials or OBF token validity |
| `WAITING_FOR_HOST_TIMEOUT` | Authorized user didn't join in time | Increase timeout or ensure user joins promptly |
| `CANNOT_JOIN_MEETING` | Generic join failure | Could be invalid token, meeting ended, or meeting doesn't exist |

### Credential-Specific Errors

When using stored credentials, check the credential's error state:

```bash
curl "https://api.meetingbaas.com/v2/zoom-credentials/b2c3d4e5-f6a7-8901-bcde-f12345678901" \
     -H "x-meeting-baas-api-key: YOUR-API-KEY"
```

If `state` is `invalid`, the `last_error_message` explains what went wrong.

## Required Scopes

Your Zoom app needs these scopes for OBF tokens:

| Scope | Purpose | Required? |
|-------|---------|-----------|
| `user:read:zak` | Auto-added when enabling Meeting SDK | Yes (auto) |
| `user:read:token` | Fetch OBF tokens | Yes |
| `user:read:user` | Get Zoom user profile | Yes (for stored credentials) |

See [Zoom App Setup](/docs/api/getting-started/zoom/app-setup) for adding scopes.

## Migration Checklist

<Steps>
<Step>
### Determine if You Need OBF Tokens

Do your bots join meetings hosted by external Zoom accounts? If yes, you need OBF tokens. If only internal meetings, use [app-only credentials](/docs/api-v2/authenticated-bots/zoom/credentials#app-only-credentials).

</Step>

<Step>
### Configure Your Zoom App

Ensure your Zoom app has `user:read:token` and `user:read:user` scopes. See [Zoom App Setup](/docs/api/getting-started/zoom/app-setup).

</Step>

<Step>
### Choose an Integration Option

- **Option 1 (Stored Credential)**: Recommended for most users
- **Option 2 (User ID Lookup)**: If you track Zoom user IDs
- **Option 3 (Direct Token)**: For testing or existing OAuth
- **Option 4 (Token URL)**: Keep credentials on your infrastructure

</Step>

<Step>
### Implement OAuth Consent Flow

Build a "Connect Zoom" button for your users. See [OAuth Consent Flow](/docs/api-v2/authenticated-bots/zoom/oauth-consent-flow).

</Step>

<Step>
### Update Bot Creation Requests

Add `zoom_config` with your chosen option to bot requests.

</Step>

<Step>
### Test Before March 2, 2026

Test with real Zoom meetings before the enforcement date.

</Step>
</Steps>

## FAQ

<Accordions type="single">

<Accordion title="What happens if I don't implement OBF tokens by March 2, 2026?">
Bots joining external Zoom meetings will fail. You'll receive a join failure error. Internal meetings with SDK credentials are not affected.
</Accordion>

<Accordion title="Can one OBF token be used for multiple meetings?">
Yes, OBF tokens are not meeting-specific when fetched without specifying a meeting number.
</Accordion>

<Accordion title="Do Google Meet and Teams bots need OBF tokens?">
No, OBF tokens are Zoom-specific.
</Accordion>

<Accordion title="What if the authorized user's Zoom account is deactivated?">
The stored credential becomes invalid. The user would need to re-authorize your app.
</Accordion>

<Accordion title="Is there an alternative for continuous recording without user presence?">
Zoom is developing Real-Time Media Streams (RTMS) for this use case. We're working on RTMS support, but it has different constraints and capabilities.
</Accordion>

<Accordion title="How does v2 differ from v1 for OBF tokens?">
v2 introduces the Credentials API for secure token storage, the `zoom_config` object for cleaner configuration, and better error tracking with credential states.
</Accordion>

</Accordions>

## Resources

- [Zoom Credentials API](/docs/api-v2/authenticated-bots/zoom/credentials) — Store and manage credentials
- [OAuth Consent Flow](/docs/api-v2/authenticated-bots/zoom/oauth-consent-flow) — Build user authorization
- [Zoom App Setup](/docs/api/getting-started/zoom/app-setup) — Create your Zoom app
- [Zoom's OBF Blog Post](https://developers.zoom.us/blog/transition-to-obf-token-meetingsdk-apps/) — Official announcement
- [Zoom's OBF FAQ](https://developers.zoom.us/docs/meeting-sdk/obf-faq/) — Detailed Q&A from Zoom
- [Zoom Token API](https://developers.zoom.us/docs/api/rest/reference/user/methods/#operation/userToken) — Zoom's token endpoint


---

## Events

Work with calendar events and schedule recordings

### Source: ./content/docs/api/getting-started/calendars/events.mdx


After [setting up your calendar integration](/docs/api/getting-started/calendars/setup), you can work with calendar events and schedule recordings. This guide explains how to manage calendar events through the Meeting BaaS API.

## Event Management

<Steps>

<Step>
### Listing and Retrieving Events

Monitor and manage calendar events:

- List Events: <a href="/docs/api/reference/calendars/list_events" target="_blank">List Events</a> - See all upcoming meetings
- Get Event Details: <a href="/docs/api/reference/calendars/get_event" target="_blank">Get Event Details</a> - View meeting info and bot status

#### List Events Example

```bash
curl -X GET "https://api.meetingbaas.com/calendars/cal_12345abcde/events" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "start_date_gte": "2023-09-01T00:00:00Z",
    "start_date_lte": "2023-09-08T23:59:59Z",
    "updated_at_gte": "2023-08-29T18:30:00Z"  // Optional, for webhook integration
  }'
```

<Callout type="info">
  The List Events endpoint supports various filtering options:

- `calendar_id` (required) - Which calendar's events to retrieve

- `start_date_gte` - Filter events starting on or after this timestamp

- `start_date_lte` - Filter events starting on or before this timestamp

- `updated_at_gte` - Filter events updated on or after this timestamp

- `status` - Filter by meeting status ("upcoming", "past", "all") - default is "upcoming"

- `attendee_email` - Filter events with a specific attendee

- `organizer_email` - Filter events with a specific organizer

- `cursor` - For pagination through large result sets

See the <a href="/docs/api/reference/calendars/list_events" target="_blank">List Events API Reference</a> for full details.

</Callout>

#### Get Event Details Example

```bash
curl -X GET "https://api.meetingbaas.com/calendars/cal_12345abcde/events/evt_67890fghij" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

</Step>

<Step>
### Understanding Meeting Links

Meeting BaaS automatically detects meeting links within calendar events and can deploy bots based on your configuration.

<Callout type="info">
  Meeting links are detected from the event location, description, or custom
  properties depending on the calendar provider, habits or integrations of the
  user.
</Callout>

Each platform has its own link format that our system automatically recognizes.

</Step>

<Step>
### Recording Management

You can schedule or cancel recording events for specific calendar events:

- Schedule Recording: <a href="/docs/api/reference/calendars/schedule_record_event" target="_blank">Schedule Record Event</a> - Configure a bot to record a specific event
- Cancel Recording: <a href="/docs/api/reference/calendars/unschedule_record_event" target="_blank">Unschedule Record Event</a> - Cancel a scheduled recording

#### Schedule Recording Example

```bash
curl -X POST "https://api.meetingbaas.com/calendars/cal_12345abcde/events/evt_67890fghij/record" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recording_mode": "speaker_view",
    "include_transcription": true,
    "bot_name": "Recording Bot",
    "bot_avatar_url": "https://example.com/avatar.png",
    "entry_message": "This meeting is being recorded for note-taking purposes."
  }'
```

<Callout type="warn">
  When scheduling a recording, the bot will automatically join the meeting at
  the scheduled time and begin recording based on your configuration.
</Callout>

</Step>

<Step>
### Recording Options

The recording configuration supports the same options as manual bot deployment:

#### Visual Recording Options:

- `speaker_view` - Records the active speaker (default)
- `gallery_view` - Records all participants in a grid layout
- `audio_only` - Records only the audio from the meeting

#### Additional Features:

- `include_transcription: true|false` - Generate speech-to-text transcription
- `bot_name: "string"` - Custom name for the bot in the meeting
- `bot_avatar_url: "url"` - Custom profile picture for the bot
- `entry_message: "string"` - Message the bot will send upon joining

#### Canceling a Scheduled Recording

To cancel a scheduled recording:

```bash
curl -X DELETE "https://api.meetingbaas.com/calendars/cal_12345abcde/events/evt_67890fghij/record" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

<Callout type="info">
  Remember to handle webhook notifications to track the recording status and
  receive the final recording data. These updates can be used to determine
  whether to record new or updated meetings.
</Callout>

</Step>

</Steps>

## Next Steps

Now that you understand how to work with calendar events:

- Learn about [webhooks for calendar updates](/docs/api/getting-started/calendars/webhooks)
- Explore [custom meeting bot configurations](/docs/api/getting-started/sending-a-bot)
- Check out our [Live Meeting Updates](/docs/api/getting-started/getting-the-data) system


---

## Calendar Synchronization

Implement your own business logic fast and reliably

### Source: ./content/docs/api/getting-started/calendars/index.mdx


Meeting BaaS allows you to automatically sync calendars from Outlook and Google Workspace to deploy bots to scheduled meetings.

This helps you:

- **Automate recording and participation** in meetings without manual intervention

- **Implement your own business logic** with simple patching:
  - Apply business rules to new calendar events, and an initial patch for existing events registered by a given user
  - Update logic as your requirements change

<div className="grid grid-cols-1 gap-4 mt-0 md:grid-cols-2 lg:grid-cols-3">
  <Card title="1. Calendar Sync Setup" href="/docs/api/getting-started/calendars/setup" icon={<ChevronRight className="w-4 h-4" />}>
    Learn how to authenticate and set up calendar integrations with Google Workspace and Microsoft Outlook
  </Card>

<Card
  title="2. Managing Calendar Events"
  href="/docs/api/getting-started/calendars/events"
  icon={<ChevronRight className="h-4 w-4" />}
>
  Work with calendar events and schedule automated recordings for meetings
</Card>

  <Card title="3. Webhooks & Maintenance" href="/docs/api/getting-started/calendars/webhooks" icon={<ChevronRight className="w-4 h-4" />}>
    Receive real-time updates, handle errors, and maintain your calendar integrations
  </Card>
</div>

## Key Benefits

- **Automated Bot Deployment**: Automatically send bots to meetings as they appear on calendars
- **Multi-Calendar Support**: Connect to both Google Workspace and Microsoft Outlook calendars
- **Real-Time Updates**: Receive webhook notifications when calendar events change
- **Selective Recording**: Apply business logic to determine which meetings to record

## Implementation Overview

1. First, [set up calendar integrations](/docs/api/getting-started/calendars/setup) using OAuth authentication
2. Then, [work with calendar events](/docs/api/getting-started/calendars/events) to schedule automated recordings
3. Finally, [implement webhooks](/docs/api/getting-started/calendars/webhooks) to receive real-time updates and handle maintenance

## Customizable Business Logic

One of the core strengths of our calendar synchronization API is how easily you can integrate your own business logic. The abstraction layer we provide gives you:

- **Flexible Decision Making**: Decide which meetings should be recorded based on any criteria you define (participants, meeting titles, domains, etc.)
- **Custom Automation Rules**: Automatically apply different recording configurations based on meeting types
- **Error Handling Control**: Implement your own retry logic and error handling strategies tailored to your application's needs
- **Integration with Your Systems**: Build your own connections to your workflows or other business systems

#### For example, you might implement rules to only record meetings:

- With external clients but not internal team meetings
- That contain specific keywords in the title or description
- Where the organizer has opted into recording
- During specific business hours or for particular teams
- With specific participant emails

The webhook-based architecture ensures your business logic operates in real-time as calendar changes occur, giving you complete control over your meeting automation.


---

## Maintenance

Maintain your calendar integrations, handle errors, and clean up calendar accounts

### Source: ./content/docs/api/getting-started/calendars/maintenance.mdx


## Error Handling and Troubleshooting

### Common Errors

#### OAuth Token Expiration

Both your app's credentials (service level) and user's credentials (user level) can expire or be revoked. If this happens _Calendar sync operations will start failing_.

##### Detecting and fixing the issue

1. You can detect this by periodically checking the calendar status using the [Get Calendar](/docs/api/reference/calendars/get_calendar) endpoint
2. You should implement a monitoring strategy using this route to detect these failures and prompt users to reconnect their calendars

When this occurs, you need to, depending on whether it is your app's credentials or the user's credentials that are expired, you have 2 choices:

<Steps>

<Step>
User's credentials are expired

Prompt the user to reauthorize calendar access by:

1. Updating your database to mark the calendar integration as requiring reauthorization
2. Prompting the user to reconnect their calendar when they next access your application

</Step>

<Step>
Your app's credentials are expired

Reauthorize your app's credentials by:

1. Requiring new app credentials as shown in the [Setup](/docs/api/getting-started/calendars/setup) guide and storing them in your database
2. Patching the calendar integration with the new credentials using the [Update Calendar](/docs/api/reference/calendars/update_calendar) endpoint to update your app credentials while keeping the same user credentials

</Step>

</Steps>

### Rate Limiting Considerations

Calendar APIs enforce rate limits. Meeting BaaS handles these gracefully, but if you encounter persistent sync issues, check:

1. The frequency of your calendar operations
2. The number of events being synced
3. Other applications using the same OAuth credentials

<Callout type="info">
  For Google Workspace, you're limited to 1 million queries per day per project.
  For Microsoft, limits vary by subscription type.
</Callout>

If you're building a high-volume application, consider implementing these best practices:

- Batch calendar operations where possible
- Implement exponential backoff for retries
- Monitor your API usage with logging and alerts
- Consider using multiple projects for very high-volume needs

## Maintenance and Cleanup

<Steps>

<Step>
### Removing Calendar Integrations

To remove a calendar integration, use the <a href="/docs/api/reference/calendars/delete_calendar" target="_blank">Delete Calendar</a> endpoint.

```bash
curl -X DELETE "https://api.meetingbaas.com/calendars/cal_12345abcde" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

This will:

1. Stop syncing the calendar
2. Cancel any scheduled recordings for events from this calendar
3. Remove the calendar integration from your account

<Callout type="info">
  This operation does not revoke OAuth access. However MeetingBaaS will have completely deleted the calendar integration from your account and its records.

To completely remove access, users should also revoke access via Google or Microsoft security settings.
users should also revoke access via Google or Microsoft security settings.

</Callout>
</Step>

</Steps>

## Next Steps

Now that you've mastered calendar synchronization:

- Learn about [custom meeting bot configurations](/docs/api/getting-started/sending-a-bot)
- Explore our [Live Meeting Updates](/docs/api/getting-started/getting-the-data) for real-time meeting data
- Check out our [Community & Support](/docs/api/community-and-support) resources


---

## Setup

### Source: ./content/docs/api/getting-started/calendars/setup.mdx


Meeting BaaS allows you to automatically sync calendars from Outlook and Google Workspace to deploy bots to scheduled meetings.

## Prerequisites

Before starting the calendar sync integration, ensure you have:

- An active Meeting BaaS account with API access
- A webhook endpoint configured in your Meeting BaaS account to receive calendar event notifications
- Developer access to Google Cloud Console and/or Microsoft Entra ID

<Steps>

<Step>

### Authentication: Get your OAuth credentials

To start syncing calendars, you'll need two sets of credentials:

1. **Your App's Credentials (Service Level)**

- For Outlook: Your app's Microsoft Client ID and Client Secret
- For Google Workspace: Your app's Google Client ID and Client Secret

2. **End User's Credentials (User Level)**

- OAuth refresh token obtained when user grants calendar access to your app

<Callout type="info">
  Best Practice: Request calendar access as a separate step after initial user
  signup. Users are more likely to grant calendar access when it's clearly tied
  to a specific feature they want to use.
</Callout>

#### Required OAuth Scopes

For **Google Workspace**:

- `https://www.googleapis.com/auth/calendar.readonly` - To read calendar and event data
- `https://www.googleapis.com/auth/calendar.events.readonly` - To access event details

<Callout type="warn">
  **Important**: Make sure the Google Calendar API is enabled for the Google Cloud project that owns your OAuth client. In Google Cloud Console, switch to the correct project, then go to "APIs & Services" > "Library" > "Google Calendar API" and click "Enable". You can also open the API page directly: [Google Calendar API](https://console.cloud.google.com/apis/library/calendar-json.googleapis.com).
  If you encounter a "500 Internal Server Error" when creating a calendar integration, verify that this API is enabled and retry.
</Callout>
For **Microsoft Outlook**:

- `Calendars.Read` - To read calendar and event data
- `Calendars.ReadWrite` - Required if you need to modify calendar events

</Step>

<Step>

### Optional: List Raw Calendars

Before syncing calendars, you can use the <a href="/docs/api/reference/calendars/list_raw_calendars" target="_blank">List Raw Calendars</a> endpoint to view all your user's available calendars. This is particularly useful when a user has multiple calendars and you need to choose which ones to sync.

#### Request Example

```bash
curl -X GET "https://api.meetingbaas.com/calendars/raw" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "google",
    "refresh_token": "USER_REFRESH_TOKEN",
    "client_id": "YOUR_GOOGLE_CLIENT_ID",
    "client_secret": "YOUR_GOOGLE_CLIENT_SECRET"
  }'
```

#### Response Example

```json
{
  "calendars": [
    {
      "id": "primary",
      "name": "Main Calendar",
      "description": "User's primary calendar",
      "is_primary": true
    },
    {
      "id": "team_calendar@group.calendar.google.com",
      "name": "Team Calendar",
      "description": "Shared team meetings",
      "is_primary": false
    }
  ]
}
```

<Callout type="info">
  Calendar IDs differ between providers. Google uses email-like IDs, while
  Microsoft uses GUID formats.
</Callout>

</Step>

<Step>
### Create a Calendar Integration

Create a new calendar integration by calling the <a href="/docs/api/reference/calendars/create_calendar" target="_blank">Create Calendar</a> endpoint with the previously obtained credentials.

#### Request Example

```bash
curl -X POST "https://api.meetingbaas.com/calendars" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "google",
    "refresh_token": "USER_REFRESH_TOKEN",
    "client_id": "YOUR_GOOGLE_CLIENT_ID",
    "client_secret": "YOUR_GOOGLE_CLIENT_SECRET",
    "raw_calendar_id": "team_calendar@group.calendar.google.com"  // Optional
  }'
```

If the `raw_calendar_id` parameter is not provided, Meeting BaaS will sync the user's primary calendar by default.

#### Response Example

```json
{
  "id": "cal_12345abcde",
  "provider": "google",
  "status": "syncing",
  "created_at": "2023-08-15T14:30:00Z",
  "raw_calendar_id": "team_calendar@group.calendar.google.com",
  "calendar_name": "Team Calendar"
}
```

<Callout type="warn">
  Store the returned calendar ID safely - you'll need it for future operations.
</Callout>

</Step>

<Step>
### Managing Calendars

Once authenticated, you can manage your calendars as described in the [Managing Calendar Events](/docs/api/getting-started/calendars/events) guide.

<Callout type="info">
  After initial setup, Meeting BaaS handles all calendar API interactions. You
  only need to respond to webhook events for calendar changes, which are covered
  in the [webhooks guide](/docs/api/getting-started/calendars/webhooks).
</Callout>

Calendar integrations can have the following status values:

- `syncing` - Initial sync in progress
- `active` - Calendar is actively syncing
- `error` - Sync encountered an error (check the `error_message` field)
- `disconnected` - The calendar connection has been terminated

</Step>

</Steps>

## Next Steps

Now that you've set up your calendar integration:

- Learn how to [manage calendar events and recordings](/docs/api/getting-started/calendars/events)
- Set up [webhooks for calendar updates](/docs/api/getting-started/calendars/webhooks)
- Explore our [main API documentation](/docs/api) for other features


---

## Webhooks

Receive real-time updates, handle errors, and maintain your calendar integrations

### Source: ./content/docs/api/getting-started/calendars/webhooks.mdx


## Webhook Integration

<Steps>

<Step>
### Understanding Calendar Webhooks

In addition to live meeting events via the <a href="/docs/api/getting-started/getting-the-data" target="_blank">Live Meeting Updates</a>, your Meeting BaaS webhook endpoint defined in your account will receive calendar sync events with the type `calendar.sync_events`. These events notify you about:

- New meeting schedules
- Meeting changes or cancellations
- Calendar sync status updates

When a calendar change is detected, a webhook is sent to your registered endpoint. This allows you to take real-time actions, such as scheduling a recording for new meetings or updating your database.

</Step>

<Step>
### Webhook Payload Structure

#### Example Webhook Payload

```json
{
  "event": "calendar.sync_events",
  "data": {
    "calendar_id": "cal_12345abcde",
    "last_updated_ts": "2023-09-01T15:30:45Z",
    "affected_event_uuids": [
      "evt_67890fghij",
      "evt_12345abcde",
      "evt_98765zyxwv"
    ]
  }
}
```

The payload includes:

- `event`: The type of webhook event (e.g., `calendar.sync_events`)
- `data`: Contains the details about what changed:
  - `calendar_id`: The ID of the calendar that had changes
  - `last_updated_ts`: When the changes occurred (UTC timestamp)
  - `affected_event_uuids`: Array of event IDs that were added, updated, or deleted

<Callout type="info">
  All webhook timestamps are in UTC format. Always process timestamps
  accordingly in your application.
</Callout>

</Step>

<Step>
### Processing Webhook Updates

There are two approaches to processing calendar updates:

#### 1. Using the last_updated_ts timestamp

This approach gets all events updated after a certain timestamp:

```javascript
app.post('/webhooks/meeting-baas', async (req, res) => {
  const event = req.body;

  // Acknowledge receipt immediately with 200 OK
  res.status(200).send('Webhook received');

  // Process the event asynchronously
  if (event.event === 'calendar.sync_events') {
    try {
      // Fetch updated events
      const updatedEvents = await fetchUpdatedEvents(
        event.data.calendar_id,
        event.data.last_updated_ts,
      );

      // Process each event based on your business logic
      for (const evt of updatedEvents) {
        if (shouldRecordMeeting(evt)) {
          await scheduleRecording(event.data.calendar_id, evt.id);
        } else if (wasRecordingScheduled(evt) && shouldCancelRecording(evt)) {
          await cancelRecording(event.data.calendar_id, evt.id);
        }
      }
    } catch (error) {
      // Log error and implement retry mechanism
      console.error('Failed to process calendar sync event', error);
      // Add to retry queue
    }
  }
});
```

#### 2. More efficient approach using affected_event_uuids

This approach only processes the specific events that changed:

```javascript
app.post('/webhooks/meeting-baas', async (req, res) => {
  const event = req.body;

  // Acknowledge receipt immediately with 200 OK
  res.status(200).send('Webhook received');

  // Process the event asynchronously
  if (
    event.event === 'calendar.sync_events' &&
    event.data.affected_event_uuids &&
    event.data.affected_event_uuids.length > 0
  ) {
    try {
      // Process only the specific affected events
      for (const eventUuid of event.data.affected_event_uuids) {
        const eventDetails = await fetchEventDetails(
          event.data.calendar_id,
          eventUuid,
        );

        // Apply your business logic
        if (shouldRecordMeeting(eventDetails)) {
          await scheduleRecording(event.data.calendar_id, eventUuid);
        } else if (
          wasRecordingScheduled(eventDetails) &&
          shouldCancelRecording(eventDetails)
        ) {
          await cancelRecording(event.data.calendar_id, eventUuid);
        }
      }
    } catch (error) {
      // Log error and implement retry mechanism
      console.error('Failed to process calendar sync event', error);
      // Add to retry queue
    }
  }
});
```

<Callout type="info">
  This allows you to:

- Track all calendar changes in real-time

- Decide whether to record new or modified meetings

- Keep your system synchronized with the latest meeting data

</Callout>

<Callout type="warn">
  Always return a 200 OK response promptly to acknowledge receipt of the webhook
  before processing the data. This prevents webhook retry mechanisms from
  sending duplicate events.
</Callout>

</Step>

<Step>
### Webhook Best Practices

#### Idempotent Processing

Implement idempotent webhook processing - you may receive the same webhook multiple times in rare circumstances:

```javascript
// Example of idempotent processing using a processed events cache
const processedEvents = new Set();

app.post('/webhooks/meeting-baas', async (req, res) => {
  const event = req.body;
  const eventId = `${event.event}-${event.data.calendar_id}-${event.data.last_updated_ts}`;

  // Always acknowledge receipt immediately
  res.status(200).send('Webhook received');

  // Skip if we've already processed this exact event
  if (processedEvents.has(eventId)) {
    console.log(`Skipping already processed event: ${eventId}`);
    return;
  }

  // Add to processed events before processing
  processedEvents.add(eventId);

  // Process the event...
  // ...

  // In a production environment, you would use a persistent store
  // like Redis or a database instead of an in-memory Set
});
```

#### Retry Logic

Configure your webhook endpoint to process these real-time updates and implement appropriate retry logic for reliability:

```javascript
async function processWithRetry(fn, maxRetries = 3, delay = 1000) {
  let retries = 0;

  while (retries < maxRetries) {
    try {
      return await fn();
    } catch (error) {
      retries++;

      if (retries >= maxRetries) {
        throw error;
      }

      // Exponential backoff
      await new Promise((resolve) =>
        setTimeout(resolve, delay * Math.pow(2, retries - 1)),
      );
    }
  }
}

// Example usage
app.post('/webhooks/meeting-baas', async (req, res) => {
  // Always acknowledge receipt immediately
  res.status(200).send('Webhook received');

  // Process with retry logic
  try {
    await processWithRetry(async () => {
      // Your processing logic here
    });
  } catch (error) {
    console.error('Failed after multiple retries', error);
    // Log to monitoring system, add to dead letter queue, etc.
  }
});
```

</Step>

</Steps>

## Next Steps

Now that you understand webhooks and error handling:

- Learn how to [maintain and clean up your calendar integrations](/docs/api/getting-started/calendars/maintenance)


---

## Zoom App Setup

Create and configure a Zoom app for use with Meeting BaaS, including Marketplace approval

### Source: ./content/docs/api/getting-started/zoom/app-setup.mdx


# Setting Up a Zoom App

This guide walks through creating a Zoom app on the Zoom App Marketplace. You will use this app to either:

- Use SDK credentials for internal meetings (no OBF tokens needed)
- Implement OAuth for OBF tokens when joining external meetings

## Prerequisites

- A Zoom account (free or paid works)
- Access to the [Zoom App Marketplace](https://marketplace.zoom.us/)

## Creating Your Zoom App

<Steps>
<Step>
### Go to the Zoom App Marketplace

Navigate to [marketplace.zoom.us](https://marketplace.zoom.us/) and sign in. Click **Develop** in the top navigation, then **Build App**.

</Step>

<Step>
### Select General App

Choose **General App** as the app type. This is the unified app type that supports both OAuth and Meeting SDK.

Give your app a descriptive name (e.g., "Acme Recording Bot").

</Step>

<Step>
### Configure Basic Information

Fill out the **Basic Information** section:

| Field | Description |
|-------|-------------|
| App Name | Your app's display name |
| Short Description | Brief description of what your app does |
| Long Description | Detailed description (required for Marketplace listing) |
| Company Name | Your organization name |
| Developer Name | Primary contact name |
| Developer Email | Primary contact email |

**OAuth Redirect URL:** Enter the URL where you will handle OAuth callbacks. If you are only using SDK credentials without OAuth, you can set this to your app's home page (e.g., `https://yourapp.com/dashboard`).

</Step>

<Step>
### Enable Meeting SDK

Navigate to **Features** → **Embed** in the sidebar.

Toggle **Meeting SDK** to **On**.

This enables your app to join meetings using the Zoom Meeting SDK.

</Step>

<Step>
### Configure Scopes

Navigate to **Scopes** in the sidebar.

**Default scope:** When you enable Meeting SDK, Zoom automatically adds `user:read:zak`. This is required by the Meeting SDK toggle.

**Additional scopes:** Depending on your integration, you may need to add more scopes. Use this matrix to determine what you need:

| Integration Type | `user:read:zak` | `user:read:token` | `user:read:user` |
|------------------|-----------------|-------------------|------------------|
| **SDK credentials only** (internal meetings) | ✓ Auto-added | Not needed | Not needed |
| **OBF: Direct token** (you fetch tokens yourself) | ✓ Auto-added | You add to your app | You add to your app |
| **OBF: Token URL** (you host an endpoint) | ✓ Auto-added | You add to your app | You add to your app |
| **OBF: Managed OAuth** (Meeting BaaS stores tokens) | ✓ Auto-added | ✓ Required | ✓ Required |

<Callout>
**Which integration type should I use?**
- **Internal meetings only:** Use SDK credentials. No additional scopes needed.
- **External meetings, you manage OAuth:** Use Direct token or Token URL. Add scopes to your Zoom app.
- **External meetings, we manage OAuth:** Use Managed OAuth. Add both `user:read:token` and `user:read:user`.

See [OBF Token Support](/docs/api/getting-started/zoom/obf-tokens) for details on each option.
</Callout>

To add scopes, click **Add Scopes**, search for the scope name, and add it.

</Step>

<Step>
### Get Your Credentials

**OAuth credentials** (for OBF tokens):

Navigate to **Basic Information** to find:
- **Client ID** — Used in OAuth authorization URL
- **Client Secret** — Used when exchanging authorization codes

**SDK credentials** (for internal meetings and AAN attribution):

Navigate to **Features** → **Embed** → **Meeting SDK** section to find:
- **SDK Key** (Client ID) — Use as `zoom_sdk_id`
- **SDK Secret** — Use as `zoom_sdk_pwd`

See Zoom's guide: [Get Meeting SDK Credentials](https://developers.zoom.us/docs/meeting-sdk/get-credentials/#get-meeting-sdk-credentials)

</Step>
</Steps>

## Active Apps Notifier (AAN) Attribution

When a bot joins a Zoom meeting using the Meeting SDK, Zoom displays the app name in the **Active Apps Notifier (AAN)** — a notice visible to all meeting participants showing which apps are accessing meeting content. The app name shown is determined by the **SDK credentials** used to initialize the session.

<Callout type="warn">
**Zoom Marketplace Requirement:** During Marketplace review, Zoom requires the AAN to display **your** app name. If the AAN shows a different app name (e.g., "Meeting Baas" instead of your product name), the reviewer may flag this. See [Zoom's AAN documentation](https://developers.zoom.us/docs/meeting-sdk/ui-notices/#active-apps-notifier-aan-use-case) for details.
</Callout>

### How It Works

The AAN displays the app name associated with whichever SDK credentials are used for the session:

- **With your SDK credentials** (`zoom_sdk_id` / `zoom_sdk_pwd`): The AAN shows **your** app name
- **Without SDK credentials**: The bot falls back to Meeting BaaS's default SDK credentials, and the AAN shows "Meeting Baas"

### Using SDK Credentials with OBF Tokens

If your bots join external meetings (requiring OBF tokens), you should **also** pass your SDK credentials to ensure correct AAN attribution. The SDK credentials and OBF tokens serve different purposes:

- **SDK credentials** → Control the AAN app name (your app identity)
- **OBF tokens** → Authorize the bot to join on behalf of a specific user

You can combine them in the same bot request:

```json
{
  "meeting_url": "https://zoom.us/j/123456789",
  "bot_name": "Recording Bot",
  "zoom_sdk_id": "YOUR_SDK_KEY",
  "zoom_sdk_pwd": "YOUR_SDK_SECRET",
  "zoom_obf_token_url": "https://your-api.com/zoom/obf-token"
}
```

This works with any of the three OBF options (`zoom_obf_token`, `zoom_obf_token_url`, or `zoom_obf_token_user_id`).

<Callout>
**v2 API users:** In v2, you can store your SDK credentials once using the [Credentials API](/docs/api-v2/authenticated-bots/zoom/credentials) instead of passing them with every request. See [v2 Zoom Credentials](/docs/api-v2/authenticated-bots/zoom/credentials) for details.
</Callout>

Learn more: [Zoom AAN Documentation](https://developers.zoom.us/docs/meeting-sdk/ui-notices/#active-apps-notifier-aan-use-case) | [Get Meeting SDK Credentials](https://developers.zoom.us/docs/meeting-sdk/get-credentials/#get-meeting-sdk-credentials)

## Using SDK Credentials

If your bots only join meetings within your own Zoom organization, pass SDK credentials when creating bots:

<Tabs items={['Bash', 'Python', 'JavaScript']}>
  <Tab value="Bash">
    ```bash
    curl -X POST "https://api.meetingbaas.com/bots" \
         -H "Content-Type: application/json" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY" \
         -d '{
               "meeting_url": "https://zoom.us/j/123456789",
               "bot_name": "Recording Bot",
               "zoom_sdk_id": "YOUR_SDK_KEY",
               "zoom_sdk_pwd": "YOUR_SDK_SECRET"
             }'
    ```
  </Tab>
  <Tab value="Python">
    ```python
    import requests

    response = requests.post(
        "https://api.meetingbaas.com/bots",
        headers={
            "Content-Type": "application/json",
            "x-meeting-baas-api-key": "YOUR-API-KEY",
        },
        json={
            "meeting_url": "https://zoom.us/j/123456789",
            "bot_name": "Recording Bot",
            "zoom_sdk_id": "YOUR_SDK_KEY",
            "zoom_sdk_pwd": "YOUR_SDK_SECRET"
        }
    )
    print(response.json())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript
    const response = await fetch("https://api.meetingbaas.com/bots", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
      },
      body: JSON.stringify({
        meeting_url: "https://zoom.us/j/123456789",
        bot_name: "Recording Bot",
        zoom_sdk_id: "YOUR_SDK_KEY",
        zoom_sdk_pwd: "YOUR_SDK_SECRET",
      }),
    });
    console.log(await response.json());
    ```
  </Tab>
</Tabs>

<Callout type="warn">
SDK credentials only work for meetings within your Zoom account. For external meetings, you need OBF tokens. See [OBF Token Support](/docs/api/getting-started/zoom/obf-tokens).
</Callout>

## Advanced: ZAK Token URL

If you need the bot to join as a specific Zoom user (not just as an anonymous participant), you can provide a ZAK (Zoom Access Key) token via the `zoom_access_token_url` parameter.

```json
{
  "meeting_url": "https://zoom.us/j/123456789",
  "bot_name": "Recording Bot",
  "zoom_access_token_url": "https://your-api.com/zoom/zak-token"
}
```

When the bot calls your endpoint, it appends `bot_uuid` and `extra` as query parameters (same as with OBF token URLs). This lets you identify which ZAK token to return:

```
GET https://your-api.com/zoom/zak-token?bot_uuid=abc-123&extra={"user_id":"usr_456"}
```

Your endpoint should return the raw ZAK token as plain text (not JSON).

<Callout>
ZAK tokens are different from OBF tokens. ZAK tokens let the bot join **as** a specific user. OBF tokens let the bot join **on behalf of** a user (as an assistant). For most recording use cases, OBF tokens are the right choice.
</Callout>

## Submitting for Marketplace Approval

Your app does not need to be listed on the Zoom Marketplace for Meeting BaaS to work. However, approval is required if:

- You want a public listing for users to discover your app
- You want the "approved" badge for trust
- You are using OAuth and want external users to authorize your app

### App Listing Section

Fill out the **App Listing** section:

| Field | What to Enter |
|-------|---------------|
| Category | Choose the most relevant category (e.g., "Productivity") |
| Screenshots | At least 3 screenshots showing your app in action |
| Icon | 256x256 PNG with transparent background |
| Banner | 1280x640 PNG for the listing header |
| Support URL | Link to your support/help page |
| Privacy Policy URL | Link to your privacy policy |
| Terms of Use URL | Link to your terms of service |

### Technical Design Section

Zoom requires details about your app's architecture. Here is what to include:

**Technology Stack:**

You can keep this high-level. Example:

```
Frontend: React, TailwindCSS
Backend: Python/Node.js
Auth: OAuth 2.0 (your provider)
Database: PostgreSQL
Hosting: AWS/GCP/Azure
Zoom Integration: Zoom Meeting SDK via Meeting BaaS API
```

**Architecture Diagram:**

Include a simple diagram showing:
- Your application
- Meeting BaaS API
- Zoom SDK integration

This does not need to be complex. A basic flow diagram works.

### Application Development Section

| Question | Answer | Hint |
|----------|--------|------|
| Do you have a SSDLC? | **Yes** | Describe your secure development practices: code reviews, secrets management, dependency scanning, etc. Even informal practices count. |
| Does your app undergo SAST? | **Yes** (recommended) | Mention static analysis tools you use (CodeQL, Snyk, SonarQube, etc.). If you use GitHub, CodeQL is free and easy to set up. |
| Does your app undergo DAST? | **Optional** | Dynamic application security testing. Answer based on your practices. Not required for basic approval. |
| Third-party security testing? | **Optional** | Penetration testing or security audits. Not required but helps if you have them. |

<Callout>
**Small teams:** You do not need enterprise-grade security tooling. Basic practices like code reviews, dependency updates, and using a static analysis tool (even free ones like CodeQL) are sufficient for approval.
</Callout>

If you have security certifications (SOC 2, ISO 27001), upload them. They are not required but help with approval.

### Security Section

| Question | Answer | Hint |
|----------|--------|------|
| Does your app use TLS 1.2 or above? | **Yes** | Meeting BaaS uses TLS 1.2+. If you have your own backend, ensure it also uses TLS 1.2+. |
| Does your app use verification/secret tokens? | **No** (if Meeting BaaS only) | If you are only using this app with Meeting BaaS, select No. We do not use Zoom webhooks. If you have your own Zoom webhook integration, select Yes and describe your verification approach. |
| Does your app collect, store, or log user data? | **Depends** | If using Meeting BaaS only: We store meeting recordings and transcripts on your behalf. If you have additional data collection, describe it honestly. |

<Callout>
**Meeting BaaS users:** For most security questions, you can reference that your Zoom integration uses Meeting BaaS, which handles the SDK integration securely. You are responsible for your own application's security practices.
</Callout>

### Privacy Section

| Question | Answer | Hint |
|----------|--------|------|
| Does your app collect info from users under 16? | **No** | Unless your app specifically targets minors, answer No. Include age restrictions in your Terms of Service. |
| Is your app intended for education, healthcare, or government? | **Depends** | Answer based on your target market. If yes, you may need additional compliance documentation (FERPA, HIPAA, etc.). |
| Does your app share data with third parties? | **Depends** | If using Meeting BaaS: Yes, we use Meeting BaaS for recording infrastructure. Describe this in your privacy policy. |

Provide excerpts from your privacy policy covering:
- What data you collect
- How you use the data
- User data access rights
- How users can exercise those rights

<Callout>
**Privacy policy tip:** If you use Meeting BaaS, mention that recordings are processed by a third-party service (Meeting BaaS) and link to our privacy policy. Example: "Meeting recordings are processed by Meeting BaaS. See their privacy policy at meetingbaas.com/privacy."
</Callout>

### Submitting for Review

<Steps>
<Step>
### Verify Your Domain

Zoom requires domain verification. Follow the instructions in the **Domain Verification** section.

</Step>

<Step>
### Provide Test Credentials

Create a test account that Zoom reviewers can use to test your app. Include:
- Login credentials
- Any setup instructions
- Sample meeting URLs they can test with

</Step>

<Step>
### Submit

Click **Submit** to enter the review queue.

</Step>
</Steps>

### Review Process

**Usability Review:**
- A Zoom reviewer logs into your app using the test credentials
- They test the meeting recording/transcription flow
- If issues are found, you will receive a "more information required" request

**Security Review:**
- Zoom tests for common vulnerabilities
- They may use tools like Burp Suite to check for issues
- Ensure server-side validation for all sensitive operations

**Timeline:** Expect 1-2 weeks for review. You can request expedited review for urgent cases.

<Callout>
**Tip:** If you receive a "more information required" request, ask for a call. A 30-minute meeting with the reviewer often resolves issues faster than back-and-forth emails.
</Callout>

## Scopes Reference

| Scope | Purpose | When Needed |
|-------|---------|-------------|
| `user:read:zak` | ZAK token access | Auto-added when you enable Meeting SDK. Required for the SDK to function. |
| `user:read:token` | OBF token access | Required for fetching OBF tokens. Add this for external meetings. |
| `user:read:user` | User profile information | Required for managed OAuth. We use this to get the `zoom_user_id` when creating connections. |

<Callout>
**For Meeting BaaS users:**
- **SDK credentials only:** No additional scopes needed beyond the auto-added `user:read:zak`
- **OBF tokens (managed OAuth):** Add both `user:read:token` and `user:read:user`
- **Existing Zoom app:** If you already have a Zoom app with other scopes, you can reuse it. Just enable Meeting SDK and add any missing scopes.
</Callout>

## Troubleshooting

### SDK Authentication Failed

If you receive `ZOOM_SDK_AUTH_FAILED`:

1. Verify SDK Key and Secret are correct
2. Ensure Meeting SDK is enabled in your app
3. Check that your app is activated (not in draft status)
4. Confirm you are using the SDK credentials, not OAuth credentials

### OAuth Token Exchange Failed

If the authorization code exchange fails:

1. Verify the redirect URI matches exactly (including trailing slashes)
2. Check that the authorization code has not expired (~10 minutes)
3. Ensure all required scopes are added to your app
4. Verify Client ID and Secret are correct

### App Rejected During Review

Common rejection reasons:

- Missing or broken test credentials
- Privacy policy does not cover required topics
- Security vulnerabilities found (check for XSS, CSRF, etc.)
- Screenshots do not match actual app functionality

## Next Steps

- [OBF Token Support](/docs/api/getting-started/zoom/obf-tokens) — If joining external meetings
- [Building OAuth Consent Flow](/docs/api/getting-started/zoom/oauth-consent-flow) — Implementing user authorization
- [Sending a Bot](/docs/api/getting-started/sending-a-bot) — Basic bot creation


---

## Zoom Integration

Overview of Zoom integration options for Meeting BaaS bots

### Source: ./content/docs/api/getting-started/zoom/index.mdx


# Zoom Integration

Meeting BaaS supports Zoom through the Zoom Meeting SDK. This section covers everything you need to set up and configure Zoom bots.

<Callout type="warn">
**Important Deadline:** Starting March 2, 2026, Zoom requires OBF tokens for bots joining external meetings. See [OBF Token Support](/docs/api/getting-started/zoom/obf-tokens) for details and [Zoom's official announcement](https://developers.zoom.us/blog/transition-to-obf-token-meetingsdk-apps/).
</Callout>

## Two Approaches

How you integrate with Zoom depends on whose meetings your bots join:

<Cards>
  <Card title="Internal Meetings Only" href="/docs/api/getting-started/zoom/app-setup#using-sdk-credentials">
    Your bots join meetings within your own Zoom organization. Use SDK credentials to make all meetings "internal." No OBF tokens needed.
  </Card>
  <Card title="External Meetings" href="/docs/api/getting-started/zoom/obf-tokens">
    Your bots join meetings hosted by external accounts. OBF tokens are required after March 2, 2026.
  </Card>
</Cards>

## Quick Decision Guide

| Your Use Case | What You Need | Guide |
|---------------|---------------|-------|
| Recording your own team's meetings | SDK credentials | [App Setup](/docs/api/getting-started/zoom/app-setup) |
| Building a product for customers | OBF tokens | [OBF Token Support](/docs/api/getting-started/zoom/obf-tokens) |
| Joining meetings hosted by others | OBF tokens | [OBF Token Support](/docs/api/getting-started/zoom/obf-tokens) |

## Pages in This Section

<Cards>
  <Card title="Zoom App Setup" href="/docs/api/getting-started/zoom/app-setup">
    Create a Zoom app on the Marketplace, enable Meeting SDK, get credentials, and submit for approval.
  </Card>
  <Card title="OBF Token Support" href="/docs/api/getting-started/zoom/obf-tokens">
    Implement OBF tokens for external meetings. Covers all three integration options.
  </Card>
  <Card title="Building OAuth Consent Flow" href="/docs/api/getting-started/zoom/oauth-consent-flow">
    Step-by-step guide for implementing the OAuth consent flow in your application.
  </Card>
</Cards>

## Key Concepts

### SDK Credentials vs OBF Tokens

**SDK credentials** (`zoom_sdk_id` and `zoom_sdk_pwd`) authenticate your Zoom app itself. When you use your own SDK credentials, meetings joined by your bots are considered "internal" to your Zoom account.

**OBF tokens** authenticate on behalf of a specific Zoom user. They are required when joining meetings hosted by accounts outside your organization.

### The March 2026 Deadline

Zoom is enforcing OBF tokens starting March 2, 2026. After this date:

- Bots joining external meetings without OBF tokens will fail
- Bots joining internal meetings (with SDK credentials) are unaffected
- Google Meet and Microsoft Teams bots are unaffected

### Authorized User Presence

When using OBF tokens, the Zoom user who authorized your app must be present in the meeting. If they leave, the bot disconnects. This is a Zoom platform requirement.

For continuous recording scenarios, Zoom is developing RTMS (Real-Time Media Streams) as an alternative. We are working on RTMS support.

## Related Resources

- [Sending a Bot](/docs/api/getting-started/sending-a-bot) — Basic bot creation guide
- [Zoom's OBF Blog Post](https://developers.zoom.us/blog/transition-to-obf-token-meetingsdk-apps/) — Official announcement
- [Zoom's OBF FAQ](https://developers.zoom.us/docs/meeting-sdk/obf-faq/) — Detailed Q&A


---

## Building OAuth Consent Flow

Implement the Zoom OAuth consent flow in your application for OBF token support

### Source: ./content/docs/api/getting-started/zoom/oauth-consent-flow.mdx


# Building an OAuth Consent Flow for Zoom

This guide walks through implementing the OAuth consent flow in your application. You need this if you are using the [Managed OAuth](/docs/api/getting-started/zoom/obf-tokens#option-3-managed-oauth-zoom_obf_token_user_id) option for OBF tokens.

## Overview

The flow works like this:

1. User clicks "Connect Zoom" in your app
2. User is redirected to Zoom's authorization page
3. User authorizes your app
4. Zoom redirects back to your app with an authorization code
5. You send the code to Meeting BaaS
6. Meeting BaaS stores the tokens
7. Future bot requests use the stored connection

## Prerequisites

- A Zoom app with Meeting SDK enabled ([see App Setup](/docs/api/getting-started/zoom/app-setup))
- The `user:read:token` and `user:read:user` scopes added to your app
- A callback URL configured in your Zoom app

## Step 1: Create the Authorization URL

Build the Zoom OAuth authorization URL:

```
https://zoom.us/oauth/authorize?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}
```

| Parameter | Description |
|-----------|-------------|
| `client_id` | Your Zoom app's Client ID |
| `redirect_uri` | URL-encoded redirect URI (must match your Zoom app config) |

<Tabs items={['JavaScript', 'Python']}>
  <Tab value="JavaScript">
    ```javascript
    function getZoomAuthUrl() {
      const clientId = process.env.ZOOM_CLIENT_ID;
      const redirectUri = encodeURIComponent('https://yourapp.com/zoom/callback');

      return `https://zoom.us/oauth/authorize?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}`;
    }

    // In your route handler
    app.get('/connect-zoom', (req, res) => {
      res.redirect(getZoomAuthUrl());
    });
    ```
  </Tab>
  <Tab value="Python">
    ```python
    from urllib.parse import urlencode
    from flask import redirect

    def get_zoom_auth_url():
        client_id = os.environ['ZOOM_CLIENT_ID']
        redirect_uri = 'https://yourapp.com/zoom/callback'

        params = urlencode({
            'response_type': 'code',
            'client_id': client_id,
            'redirect_uri': redirect_uri
        })

        return f'https://zoom.us/oauth/authorize?{params}'

    @app.route('/connect-zoom')
    def connect_zoom():
        return redirect(get_zoom_auth_url())
    ```
  </Tab>
</Tabs>

### Adding State Parameter (Recommended)

Include a `state` parameter to prevent CSRF attacks and track user context:

```javascript
function getZoomAuthUrl(userId) {
  const clientId = process.env.ZOOM_CLIENT_ID;
  const redirectUri = encodeURIComponent('https://yourapp.com/zoom/callback');

  // State contains user ID and random token for CSRF protection
  const state = Buffer.from(JSON.stringify({
    userId: userId,
    csrf: crypto.randomBytes(16).toString('hex')
  })).toString('base64');

  // Store CSRF token in session for verification
  req.session.zoomCsrf = state;

  return `https://zoom.us/oauth/authorize?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&state=${state}`;
}
```

## Step 2: Handle the Callback

After the user authorizes, Zoom redirects to your callback URL with an authorization code:

```
https://yourapp.com/zoom/callback?code=AUTHORIZATION_CODE
```

<Tabs items={['JavaScript', 'Python']}>
  <Tab value="JavaScript">
    ```javascript
    app.get('/zoom/callback', async (req, res) => {
      const { code, state } = req.query;

      if (!code) {
        return res.status(400).send('Missing authorization code');
      }

      // Verify state if you used it
      if (state !== req.session.zoomCsrf) {
        return res.status(403).send('Invalid state parameter');
      }

      try {
        // Send to Meeting BaaS
        const connection = await createZoomConnection(code);

        // Store the zoom_user_id with your user
        await saveZoomConnection(req.user.id, connection.zoom_user_id);

        res.redirect('/dashboard?zoom_connected=true');
      } catch (error) {
        console.error('Zoom OAuth error:', error);
        res.redirect('/dashboard?zoom_error=true');
      }
    });
    ```
  </Tab>
  <Tab value="Python">
    ```python
    @app.route('/zoom/callback')
    def zoom_callback():
        code = request.args.get('code')
        state = request.args.get('state')

        if not code:
            return 'Missing authorization code', 400

        # Verify state if you used it
        if state != session.get('zoom_csrf'):
            return 'Invalid state parameter', 403

        try:
            # Send to Meeting BaaS
            connection = create_zoom_connection(code)

            # Store the zoom_user_id with your user
            save_zoom_connection(current_user.id, connection['zoom_user_id'])

            return redirect('/dashboard?zoom_connected=true')
        except Exception as e:
            print(f'Zoom OAuth error: {e}')
            return redirect('/dashboard?zoom_error=true')
    ```
  </Tab>
</Tabs>

## Step 3: Create the Zoom OAuth Connection

Send the authorization code to Meeting BaaS to exchange it for tokens:

<Tabs items={['JavaScript', 'Python']}>
  <Tab value="JavaScript">
    ```javascript
    async function createZoomConnection(authorizationCode) {
      const response = await fetch('https://api.meetingbaas.com/zoom_oauth_connections', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-meeting-baas-api-key': process.env.MEETING_BAAS_API_KEY,
        },
        body: JSON.stringify({
          authorization_code: authorizationCode,
          redirect_uri: 'https://yourapp.com/zoom/callback',
          zoom_client_id: process.env.ZOOM_CLIENT_ID,
          zoom_client_secret: process.env.ZOOM_CLIENT_SECRET,
        }),
      });

      if (!response.ok) {
        const error = await response.json();
        throw new Error(`Failed to create connection: ${error.message}`);
      }

      return response.json();
    }
    ```
  </Tab>
  <Tab value="Python">
    ```python
    import requests

    def create_zoom_connection(authorization_code):
        response = requests.post(
            'https://api.meetingbaas.com/zoom_oauth_connections',
            headers={
                'Content-Type': 'application/json',
                'x-meeting-baas-api-key': os.environ['MEETING_BAAS_API_KEY'],
            },
            json={
                'authorization_code': authorization_code,
                'redirect_uri': 'https://yourapp.com/zoom/callback',
                'zoom_client_id': os.environ['ZOOM_CLIENT_ID'],
                'zoom_client_secret': os.environ['ZOOM_CLIENT_SECRET'],
            }
        )

        response.raise_for_status()
        return response.json()
    ```
  </Tab>
</Tabs>

**Response:**

```json
{
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "zoom_user_id": "SeJwoMGwTCu52501SbDC0Q",
  "zoom_account_id": "AplWZ5oMSouJOw9zu0cmKQ",
  "state": "connected",
  "scopes": "user:read:token user:read:user",
  "created_at": "2026-02-08T16:00:00",
  "updated_at": "2026-02-08T16:00:00"
}
```

Store the `zoom_user_id` associated with your user. You will use this when creating bots.

## Step 4: Create Bots with the Connection

When creating a bot for a user who has connected their Zoom account:

<Tabs items={['JavaScript', 'Python']}>
  <Tab value="JavaScript">
    ```javascript
    async function createBot(meetingUrl, userId) {
      // Look up the user's zoom_user_id
      const zoomUserId = await getZoomUserIdForUser(userId);

      const response = await fetch('https://api.meetingbaas.com/bots', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-meeting-baas-api-key': process.env.MEETING_BAAS_API_KEY,
        },
        body: JSON.stringify({
          meeting_url: meetingUrl,
          bot_name: 'Recording Bot',
          zoom_obf_token_user_id: zoomUserId,
        }),
      });

      return response.json();
    }
    ```
  </Tab>
  <Tab value="Python">
    ```python
    def create_bot(meeting_url, user_id):
        # Look up the user's zoom_user_id
        zoom_user_id = get_zoom_user_id_for_user(user_id)

        response = requests.post(
            'https://api.meetingbaas.com/bots',
            headers={
                'Content-Type': 'application/json',
                'x-meeting-baas-api-key': os.environ['MEETING_BAAS_API_KEY'],
            },
            json={
                'meeting_url': meeting_url,
                'bot_name': 'Recording Bot',
                'zoom_obf_token_user_id': zoom_user_id,
            }
        )

        return response.json()
    ```
  </Tab>
</Tabs>

## Handling Disconnections

Users may revoke your app's access through Zoom's settings. You should:

1. Check the connection state before creating bots
2. Prompt users to reconnect if needed

<Tabs items={['JavaScript', 'Python']}>
  <Tab value="JavaScript">
    ```javascript
    async function getConnectionState(zoomUserId) {
      const connections = await fetch(
        'https://api.meetingbaas.com/zoom_oauth_connections',
        {
          headers: {
            'x-meeting-baas-api-key': process.env.MEETING_BAAS_API_KEY,
          },
        }
      ).then(r => r.json());

      const connection = connections.find(c => c.zoom_user_id === zoomUserId);
      return connection?.state || 'not_found';
    }

    // Before creating a bot
    const state = await getConnectionState(user.zoomUserId);
    if (state !== 'connected') {
      // Prompt user to reconnect
      return res.redirect('/connect-zoom');
    }
    ```
  </Tab>
  <Tab value="Python">
    ```python
    def get_connection_state(zoom_user_id):
        response = requests.get(
            'https://api.meetingbaas.com/zoom_oauth_connections',
            headers={
                'x-meeting-baas-api-key': os.environ['MEETING_BAAS_API_KEY'],
            }
        )

        connections = response.json()
        connection = next(
            (c for c in connections if c['zoom_user_id'] == zoom_user_id),
            None
        )
        return connection['state'] if connection else 'not_found'

    # Before creating a bot
    state = get_connection_state(user.zoom_user_id)
    if state != 'connected':
        # Prompt user to reconnect
        return redirect('/connect-zoom')
    ```
  </Tab>
</Tabs>

## UI Recommendations

### Connect Button

Show a clear "Connect Zoom" button for users who have not connected:

```jsx
function ZoomConnectionStatus({ isConnected, onConnect }) {
  if (isConnected) {
    return (
      <div className="flex items-center gap-2">
        <CheckIcon className="text-green-500" />
        <span>Zoom connected</span>
      </div>
    );
  }

  return (
    <button onClick={onConnect} className="btn btn-primary">
      Connect Zoom Account
    </button>
  );
}
```

### Error States

Handle common error scenarios:

| Error | User Message |
|-------|-------------|
| Authorization cancelled | "You cancelled the Zoom connection. Try again when ready." |
| Code expired | "The authorization expired. Please try connecting again." |
| Connection already exists | "Your Zoom account is already connected." |
| Invalid credentials | "There was a problem connecting to Zoom. Please contact support." |

### Reconnection Flow

When a connection becomes invalid:

```jsx
function ZoomReconnectBanner({ onReconnect }) {
  return (
    <div className="bg-yellow-50 border border-yellow-200 p-4 rounded">
      <p>Your Zoom connection needs to be renewed.</p>
      <button onClick={onReconnect} className="btn btn-secondary mt-2">
        Reconnect Zoom
      </button>
    </div>
  );
}
```

## Complete Example

Here is a complete Express.js implementation:

```javascript
const express = require('express');
const crypto = require('crypto');

const app = express();

// Environment variables
const {
  ZOOM_CLIENT_ID,
  ZOOM_CLIENT_SECRET,
  MEETING_BAAS_API_KEY,
  APP_URL
} = process.env;

const REDIRECT_URI = `${APP_URL}/zoom/callback`;

// Start OAuth flow
app.get('/connect-zoom', (req, res) => {
  const state = crypto.randomBytes(16).toString('hex');
  req.session.zoomState = state;

  const authUrl = new URL('https://zoom.us/oauth/authorize');
  authUrl.searchParams.set('response_type', 'code');
  authUrl.searchParams.set('client_id', ZOOM_CLIENT_ID);
  authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
  authUrl.searchParams.set('state', state);

  res.redirect(authUrl.toString());
});

// Handle callback
app.get('/zoom/callback', async (req, res) => {
  const { code, state, error } = req.query;

  // Handle user cancellation
  if (error) {
    return res.redirect('/settings?zoom_error=cancelled');
  }

  // Verify state
  if (state !== req.session.zoomState) {
    return res.status(403).send('Invalid state');
  }

  try {
    // Create connection in Meeting BaaS
    const response = await fetch(
      'https://api.meetingbaas.com/zoom_oauth_connections',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-meeting-baas-api-key': MEETING_BAAS_API_KEY,
        },
        body: JSON.stringify({
          authorization_code: code,
          redirect_uri: REDIRECT_URI,
          zoom_client_id: ZOOM_CLIENT_ID,
          zoom_client_secret: ZOOM_CLIENT_SECRET,
        }),
      }
    );

    if (!response.ok) {
      throw new Error('Failed to create connection');
    }

    const connection = await response.json();

    // Store zoom_user_id with your user
    await db.users.update(req.user.id, {
      zoom_user_id: connection.zoom_user_id,
      zoom_connected: true,
    });

    res.redirect('/settings?zoom_connected=true');
  } catch (error) {
    console.error('Zoom OAuth error:', error);
    res.redirect('/settings?zoom_error=failed');
  }
});

// Create a bot
app.post('/api/bots', async (req, res) => {
  const { meetingUrl } = req.body;
  const user = req.user;

  if (!user.zoom_connected) {
    return res.status(400).json({
      error: 'Please connect your Zoom account first'
    });
  }

  const response = await fetch('https://api.meetingbaas.com/bots', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-meeting-baas-api-key': MEETING_BAAS_API_KEY,
    },
    body: JSON.stringify({
      meeting_url: meetingUrl,
      bot_name: 'Recording Bot',
      zoom_obf_token_user_id: user.zoom_user_id,
    }),
  });

  res.json(await response.json());
});
```

## Next Steps

- [OBF Token Support](/docs/api/getting-started/zoom/obf-tokens) — Full OBF documentation
- [Zoom App Setup](/docs/api/getting-started/zoom/app-setup) — Creating your Zoom app
- [Sending a Bot](/docs/api/getting-started/sending-a-bot) — Bot creation basics


---

## Zoom OBF Token Support

Configure OBF (On Behalf Of) tokens for Zoom bots joining external meetings after March 2, 2026

### Source: ./content/docs/api/getting-started/zoom/obf-tokens.mdx


# Zoom OBF Token Support

Starting **March 2, 2026**, Zoom requires Meeting SDK applications to use On Behalf Of (OBF) tokens when joining meetings they did not create. This page explains what OBF tokens are, who is affected, and how to implement them with Meeting BaaS.

## What is an OBF Token?

An OBF (On Behalf Of) token is a Zoom authorization token that proves a specific Zoom user has authorized your bot to join meetings on their behalf. It is:

- **User-specific**: Each token is tied to a particular Zoom user who authorized your app
- **Short-lived**: Tokens should be fetched close to when they are needed
- **Required for external meetings**: After March 2, 2026, bots cannot join meetings hosted by external accounts without an OBF token

<Callout>
OBF tokens require the authorized user to be present in the meeting. If they leave, the bot is disconnected. This is a Zoom requirement.
</Callout>

## Who Needs OBF Tokens?

### You need OBF tokens if:

- Your bots join Zoom meetings created by people **outside** your Zoom organization
- You are building a product where your customers request meeting recordings
- You use Meeting BaaS as infrastructure for a service where end users have their own Zoom accounts

### You do NOT need OBF tokens if:

- Your bots only join meetings **within** your own Zoom account or organization
- You use your own [SDK credentials](/docs/api/getting-started/zoom/app-setup) (which makes all meetings "internal")
- You only use Google Meet or Microsoft Teams bots

## Three Integration Options

Meeting BaaS supports three ways to provide OBF tokens, from most manual to fully automated:

### Option 1: Direct Token (`zoom_obf_token`)

You fetch the OBF token yourself using Zoom's API and pass it directly when creating a bot.

**How it works:**
1. Your backend calls Zoom's API to get an OBF token
2. You pass the token in the bot creation request
3. The bot uses this token when joining

<Tabs items={['Bash', 'Python', 'JavaScript']}>
  <Tab value="Bash">
    ```bash title="direct_obf_token.sh"
    curl -X POST "https://api.meetingbaas.com/bots" \
         -H "Content-Type: application/json" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY" \
         -d '{
               "meeting_url": "https://zoom.us/j/123456789",
               "bot_name": "Recording Bot",
               "zoom_obf_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
             }'
    ```
  </Tab>
  <Tab value="Python">
    ```python title="direct_obf_token.py"
    import requests

    # First, fetch OBF token from Zoom using your stored access token
    zoom_headers = {"Authorization": f"Bearer {user_access_token}"}
    obf_response = requests.get(
        "https://api.zoom.us/v2/users/me/token?type=onbehalf",
        headers=zoom_headers
    )
    obf_token = obf_response.json()["token"]

    # Then, create bot with the OBF token
    url = "https://api.meetingbaas.com/bots"
    headers = {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
    }
    config = {
        "meeting_url": "https://zoom.us/j/123456789",
        "bot_name": "Recording Bot",
        "zoom_obf_token": obf_token
    }
    response = requests.post(url, json=config, headers=headers)
    print(response.json())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript title="direct_obf_token.js"
    // First, fetch OBF token from Zoom using your stored access token
    const obfResponse = await fetch(
      "https://api.zoom.us/v2/users/me/token?type=onbehalf",
      {
        headers: { Authorization: `Bearer ${userAccessToken}` },
      }
    );
    const { token: obfToken } = await obfResponse.json();

    // Then, create bot with the OBF token
    const response = await fetch("https://api.meetingbaas.com/bots", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
      },
      body: JSON.stringify({
        meeting_url: "https://zoom.us/j/123456789",
        bot_name: "Recording Bot",
        zoom_obf_token: obfToken,
      }),
    });
    console.log(await response.json());
    ```
  </Tab>
</Tabs>

**Pros:**
- Full control over token lifecycle
- No credentials stored in Meeting BaaS

**Cons:**
- You must implement OAuth token storage and refresh
- Token may expire if there is delay between fetching and bot joining
- Must fetch a fresh token for each bot request

**Best for:** Testing, debugging, or customers who already have Zoom OAuth infrastructure.

---

### Option 2: Token URL (`zoom_obf_token_url`)

You provide a URL that returns an OBF token. The bot calls this URL at join time to fetch a fresh token.

**How it works:**
1. You host an endpoint that returns OBF tokens
2. You pass the endpoint URL when creating a bot
3. The bot calls your endpoint at join time and receives the token

<Tabs items={['Bash', 'Python', 'JavaScript']}>
  <Tab value="Bash">
    ```bash title="token_url.sh"
    curl -X POST "https://api.meetingbaas.com/bots" \
         -H "Content-Type: application/json" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY" \
         -d '{
               "meeting_url": "https://zoom.us/j/123456789",
               "bot_name": "Recording Bot",
               "zoom_obf_token_url": "https://your-api.com/zoom/obf-token?user_id=abc123"
             }'
    ```
  </Tab>
  <Tab value="Python">
    ```python title="token_url.py"
    import requests

    url = "https://api.meetingbaas.com/bots"
    headers = {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
    }
    config = {
        "meeting_url": "https://zoom.us/j/123456789",
        "bot_name": "Recording Bot",
        "zoom_obf_token_url": "https://your-api.com/zoom/obf-token?user_id=abc123"
    }
    response = requests.post(url, json=config, headers=headers)
    print(response.json())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript title="token_url.js"
    fetch("https://api.meetingbaas.com/bots", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
      },
      body: JSON.stringify({
        meeting_url: "https://zoom.us/j/123456789",
        bot_name: "Recording Bot",
        zoom_obf_token_url: "https://your-api.com/zoom/obf-token?user_id=abc123",
      }),
    })
      .then((response) => response.json())
      .then((data) => console.log(data));
    ```
  </Tab>
</Tabs>

**Your endpoint must:**
- Accept a GET request
- Return the raw OBF token as the response body (plain text, not JSON)
- Handle OAuth token refresh internally
- Be accessible from Meeting BaaS infrastructure

**Parameters passed to your endpoint:**

When the bot calls your endpoint, it appends these query parameters:

| Parameter | Description |
|-----------|-------------|
| `bot_uuid` | The Meeting BaaS bot UUID for this request |
| `extra` | JSON string of any `extra` data you passed when creating the bot |

For example, if you create a bot with:
```json
{
  "meeting_url": "https://zoom.us/j/123456789",
  "bot_name": "Recording Bot",
  "zoom_obf_token_url": "https://your-api.com/zoom/obf-token",
  "extra": {"user_id": "usr_456", "org_id": "org_789"}
}
```

Your endpoint will receive:
```
GET https://your-api.com/zoom/obf-token?bot_uuid=abc-123-def&extra={"user_id":"usr_456","org_id":"org_789"}
```

This lets you use either our `bot_uuid` or your own identifiers in `extra` to look up which token to return.

**Example endpoint implementation:**

```python title="your_endpoint.py"
from flask import Flask, request
import requests
import json

app = Flask(__name__)

@app.route("/zoom/obf-token")
def get_obf_token():
    # Option 1: Use bot_uuid to look up the user
    bot_uuid = request.args.get("bot_uuid")

    # Option 2: Use your own identifiers from extra
    extra_str = request.args.get("extra")
    if extra_str:
        extra = json.loads(extra_str)
        user_id = extra.get("user_id")

    # Look up stored OAuth credentials for this user
    access_token = get_stored_access_token(user_id)

    # Refresh if expired
    if is_expired(access_token):
        access_token = refresh_access_token(user_id)

    # Fetch OBF token from Zoom
    response = requests.get(
        "https://api.zoom.us/v2/users/me/token?type=onbehalf",
        headers={"Authorization": f"Bearer {access_token}"}
    )

    return response.json()["token"]
```

**Pros:**
- Token is always fresh (fetched at join time)
- You maintain full control of OAuth credentials

**Cons:**
- You must host and maintain an endpoint
- You must implement OAuth token storage and refresh

**Best for:** Customers who want to keep Zoom credentials on their own infrastructure.

---

### Option 3: Managed OAuth (`zoom_obf_token_user_id`)

Meeting BaaS stores the OAuth credentials and handles token refresh automatically. This is the recommended option for most customers.

**How it works:**
1. Your Zoom user goes through OAuth consent flow
2. You send the authorization code to Meeting BaaS
3. Meeting BaaS stores the tokens and refreshes them automatically
4. When creating a bot, you just specify the Zoom user ID
5. Meeting BaaS fetches a fresh OBF token at join time

#### Step 1: Set Up OAuth Consent Flow

Direct your Zoom user to the OAuth authorization URL:

```
https://zoom.us/oauth/authorize?response_type=code&client_id={YOUR_CLIENT_ID}&redirect_uri={YOUR_REDIRECT_URI}
```

After the user authorizes, Zoom redirects to your redirect URI with an authorization code:

```
{YOUR_REDIRECT_URI}?code=AUTHORIZATION_CODE
```

#### Step 2: Create Zoom OAuth Connection

Send the authorization code to Meeting BaaS:

<Tabs items={['Bash', 'Python', 'JavaScript']}>
  <Tab value="Bash">
    ```bash title="create_connection.sh"
    curl -X POST "https://api.meetingbaas.com/zoom_oauth_connections" \
         -H "Content-Type: application/json" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY" \
         -d '{
               "authorization_code": "AUTHORIZATION_CODE_FROM_ZOOM",
               "redirect_uri": "https://your-app.com/oauth/callback",
               "zoom_client_id": "YOUR_ZOOM_CLIENT_ID",
               "zoom_client_secret": "YOUR_ZOOM_CLIENT_SECRET"
             }'
    ```
  </Tab>
  <Tab value="Python">
    ```python title="create_connection.py"
    import requests

    url = "https://api.meetingbaas.com/zoom_oauth_connections"
    headers = {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
    }
    data = {
        "authorization_code": "AUTHORIZATION_CODE_FROM_ZOOM",
        "redirect_uri": "https://your-app.com/oauth/callback",
        "zoom_client_id": "YOUR_ZOOM_CLIENT_ID",
        "zoom_client_secret": "YOUR_ZOOM_CLIENT_SECRET"
    }
    response = requests.post(url, json=data, headers=headers)
    print(response.json())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript title="create_connection.js"
    const response = await fetch(
      "https://api.meetingbaas.com/zoom_oauth_connections",
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "x-meeting-baas-api-key": "YOUR-API-KEY",
        },
        body: JSON.stringify({
          authorization_code: "AUTHORIZATION_CODE_FROM_ZOOM",
          redirect_uri: "https://your-app.com/oauth/callback",
          zoom_client_id: "YOUR_ZOOM_CLIENT_ID",
          zoom_client_secret: "YOUR_ZOOM_CLIENT_SECRET",
        }),
      }
    );
    const connection = await response.json();
    console.log(connection);
    // { uuid: "...", zoom_user_id: "SeJwoMGwTCu52501SbDC0Q", state: "connected", ... }
    ```
  </Tab>
</Tabs>

**Response:**

```json
{
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "zoom_user_id": "SeJwoMGwTCu52501SbDC0Q",
  "zoom_account_id": "AplWZ5oMSouJOw9zu0cmKQ",
  "state": "connected",
  "scopes": "user:read:token user:read:user",
  "created_at": "2026-02-08T16:00:00",
  "updated_at": "2026-02-08T16:00:00"
}
```

Save the `zoom_user_id` from the response. You will use this when creating bots.

#### Step 3: Create Bots with Managed OBF

<Tabs items={['Bash', 'Python', 'JavaScript']}>
  <Tab value="Bash">
    ```bash title="managed_obf.sh"
    curl -X POST "https://api.meetingbaas.com/bots" \
         -H "Content-Type: application/json" \
         -H "x-meeting-baas-api-key: YOUR-API-KEY" \
         -d '{
               "meeting_url": "https://zoom.us/j/123456789",
               "bot_name": "Recording Bot",
               "zoom_obf_token_user_id": "SeJwoMGwTCu52501SbDC0Q"
             }'
    ```
  </Tab>
  <Tab value="Python">
    ```python title="managed_obf.py"
    import requests

    url = "https://api.meetingbaas.com/bots"
    headers = {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
    }
    config = {
        "meeting_url": "https://zoom.us/j/123456789",
        "bot_name": "Recording Bot",
        "zoom_obf_token_user_id": "SeJwoMGwTCu52501SbDC0Q"
    }
    response = requests.post(url, json=config, headers=headers)
    print(response.json())
    ```
  </Tab>
  <Tab value="JavaScript">
    ```javascript title="managed_obf.js"
    fetch("https://api.meetingbaas.com/bots", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-meeting-baas-api-key": "YOUR-API-KEY",
      },
      body: JSON.stringify({
        meeting_url: "https://zoom.us/j/123456789",
        bot_name: "Recording Bot",
        zoom_obf_token_user_id: "SeJwoMGwTCu52501SbDC0Q",
      }),
    })
      .then((response) => response.json())
      .then((data) => console.log(data));
    ```
  </Tab>
</Tabs>

Meeting BaaS will automatically:
1. Look up the stored OAuth connection for this Zoom user
2. Refresh the access token if expired
3. Fetch a fresh OBF token from Zoom's API
4. Pass it to the bot at join time

**Pros:**
- Fully automated after initial setup
- No need to manage tokens yourself
- Token is always fresh
- Handles refresh automatically

**Cons:**
- Zoom OAuth credentials must be shared with Meeting BaaS
- Requires building an OAuth consent UI for your users

**Best for:** Most customers, especially those building products for end users.

## App Attribution (Active Apps Notifier)

When using OBF tokens, the bot's **SDK credentials** determine which app name appears in Zoom's [Active Apps Notifier (AAN)](https://developers.zoom.us/docs/meeting-sdk/ui-notices/#active-apps-notifier-aan-use-case) — the notice shown to all meeting participants identifying which app is accessing meeting content.

By default, if you only pass an OBF token without your own SDK credentials, the bot uses Meeting BaaS's default SDK credentials and the AAN will display "Meeting Baas" instead of your app name.

**To show your app name in the AAN**, pass your SDK credentials alongside your OBF token option:

```json
{
  "meeting_url": "https://zoom.us/j/123456789",
  "bot_name": "Recording Bot",
  "zoom_sdk_id": "YOUR_SDK_KEY",
  "zoom_sdk_pwd": "YOUR_SDK_SECRET",
  "zoom_obf_token_url": "https://your-api.com/zoom/obf-token"
}
```

This works with all three OBF options. The SDK credentials and OBF tokens serve different purposes — SDK credentials control the app identity (AAN), while OBF tokens authorize the bot to join on behalf of a user.

<Callout type="warn">
**Zoom Marketplace Requirement:** During Marketplace review, Zoom requires the AAN to display your app's name. If you're going through review, the reviewer may flag this if your SDK credentials are not included. See [Zoom App Setup](/docs/api/getting-started/zoom/app-setup#active-apps-notifier-aan-attribution) for details.
</Callout>

<Callout>
**Migrating to v2?** In the v2 API, you can store your SDK credentials once using the [Credentials API](/docs/api-v2/authenticated-bots/zoom/credentials) instead of passing them with every request. This is more secure and simplifies your integration.
</Callout>

## Bot Behavior with OBF Tokens

### Authorized User Not in Meeting

When the bot joins with an OBF token and the authorized user is not yet in the meeting:

1. Bot attempts to join
2. Zoom returns "Authorized user not in meeting" error
3. Bot retries every 30 seconds
4. Once the authorized user joins, the bot successfully enters
5. If the user does not join within the timeout period, the bot exits with error

The timeout is controlled by `automatic_leave.waiting_room_timeout` (default: 200 seconds).

```json
{
  "meeting_url": "https://zoom.us/j/123456789",
  "bot_name": "Recording Bot",
  "zoom_obf_token_user_id": "SeJwoMGwTCu52501SbDC0Q",
  "automatic_leave": {
    "waiting_room_timeout": 300
  }
}
```

### Authorized User Leaves

If the authorized user leaves the meeting while the bot is active, Zoom ends the SDK session. The bot will:

1. Stop recording
2. Upload any recorded content
3. Send completion webhook
4. Exit the meeting

This is a Zoom requirement and cannot be changed.

## Error Codes

| Error Code | Meaning |
|------------|---------|
| `WaitingForHostTimeout` | The authorized user did not join the meeting within the timeout period |
| `CannotJoinMeeting` | Generic join failure. With OBF, this could mean an invalid or expired token |
| `ZOOM_SDK_AUTH_FAILED` | SDK authentication failed. Check SDK credentials or OBF token validity |

## API Reference

### Zoom OAuth Connections

The following endpoints manage Zoom OAuth connections for the managed OBF flow:

- `POST /zoom_oauth_connections` — Create a new connection by exchanging an authorization code
- `GET /zoom_oauth_connections` — List all connections for your account
- `GET /zoom_oauth_connections/:uuid` — Get a specific connection
- `DELETE /zoom_oauth_connections/:uuid` — Delete a connection and remove stored tokens

See the [API Reference](/docs/api/reference/zoom--o-auth/create_zoom_oauth_connection) for full endpoint documentation.

### Bot Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `zoom_obf_token` | string | Raw OBF token (Option 1) |
| `zoom_obf_token_url` | string | URL that returns an OBF token (Option 2) |
| `zoom_obf_token_user_id` | string | Zoom user ID from a stored OAuth connection (Option 3) |

Only provide **one** of these parameters. If multiple are provided, precedence is: `zoom_obf_token` > `zoom_obf_token_url` > `zoom_obf_token_user_id`.

## Migration Checklist

<Steps>
<Step>
### Determine if You Need OBF Tokens

Do your bots join meetings hosted by external Zoom accounts? If yes, you need OBF tokens. If your bots only join meetings within your own Zoom organization, consider using [SDK credentials](/docs/api/getting-started/zoom/app-setup) instead.

</Step>

<Step>
### Create or Update Your Zoom App

Ensure your Zoom app has the `user:read:token` and `user:read:user` scopes. See [Zoom App Setup](/docs/api/getting-started/zoom/app-setup).

</Step>

<Step>
### Choose an Integration Option

- **Option 1 (Direct Token)**: For testing or if you already have OAuth infrastructure
- **Option 2 (Token URL)**: If you want to keep credentials on your infrastructure
- **Option 3 (Managed OAuth)**: Recommended for most customers

</Step>

<Step>
### Implement OAuth Consent Flow

For Options 2 and 3, you need to implement a way for Zoom users to authorize your app (e.g. a "Connect Zoom" button). For Option 1 (Direct Token), you still need a way to obtain a Zoom access token—for example, a one-time OAuth flow or script—so you can call Zoom's token API to fetch the OBF token.

</Step>

<Step>
### Update Bot Creation Requests

Add the appropriate OBF parameter (`zoom_obf_token`, `zoom_obf_token_url`, or `zoom_obf_token_user_id`) to your bot creation requests.

</Step>

<Step>
### Test Before March 2, 2026

Test your integration with real Zoom meetings before the enforcement date.

</Step>
</Steps>

## FAQ

<Accordions type="single">

<Accordion title="What happens if I do not implement OBF tokens by March 2, 2026?">
Your bots will fail to join external Zoom meetings. They will receive a join failure error.
</Accordion>

<Accordion title="Can I use one OBF token for multiple meetings?">
Yes, OBF tokens are not meeting-specific by default. When fetched via the API without specifying a meeting number, the token is valid for all meetings.
</Accordion>

<Accordion title="Do I need OBF tokens for Google Meet or Microsoft Teams?">
No, OBF tokens are a Zoom-specific requirement.
</Accordion>

<Accordion title="What if the authorized user's Zoom account is deactivated?">
The OAuth connection will become invalid. The user would need to re-authorize your app.
</Accordion>

<Accordion title="Is there an alternative to OBF tokens for continuous recording?">
Zoom is developing Real-Time Media Streams (RTMS) for continuous recording use cases. We are working on RTMS support, but it has different constraints (runs as an app inside the meeting, no bidirectional streaming support yet).
</Accordion>

</Accordions>

## Resources

- [Zoom App Setup Guide](/docs/api/getting-started/zoom/app-setup)
- [Sending a Bot](/docs/api/getting-started/sending-a-bot)
- [Zoom's Official OBF Blog Post](https://developers.zoom.us/blog/transition-to-obf-token-meetingsdk-apps/)
- [Zoom's OBF FAQ](https://developers.zoom.us/docs/meeting-sdk/obf-faq/)
- [Zoom Token API Reference](https://developers.zoom.us/docs/api/rest/reference/user/methods/#operation/userToken)


---

## Create multiple bots

### Source: ./content/docs/api-v2/reference/bots/batchCreateBots.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Create multiple bots in a single request with partial success support.
    
    Processes each bot creation request sequentially (index 0, 1, 2...). Each item is validated and processed independently. If some bots fail to create, the request still returns 201 with a `data` array containing successful creations and an `errors` array containing failures. Each error includes the `index` of the failed item in the original request array.
    
    **Processing Order:** Items are processed in the order they appear in the request array. Each item goes through the same validation and checks as a single bot creation: platform detection, transcription key availability, daily bot cap check, token availability check, and deduplication lock acquisition.
    
    **Partial Success:** The response always has `success: true`, even if all items fail. Check the `errors` array to identify failed items. The `data` array contains successfully created bots with their `bot_id` and preserved `extra` metadata. The `errors` array contains failed items with `index`, `code`, `message`, `details`, and preserved `extra` metadata.
    
    **Daily Bot Cap:** The daily bot cap is checked per item, not per batch. If the cap is reached mid-batch, subsequent items will fail with `DAILY_BOT_CAP_REACHED` error. The cap is based on bots created in the last 24 hours.
    
    **Token Reservation:** Tokens are reserved individually for each successful bot creation (0.5 tokens per bot). If token availability becomes insufficient mid-batch, subsequent items will fail with `INSUFFICIENT_TOKENS`.
    
    **Error Index Mapping:** Each error includes an `index` field (0-based) that corresponds to the item's position in the request array. Use this to correlate errors with your original request. Validation errors include detailed validation issues in the `details` field.
    
    **Error Isolation:** Each bot creation is processed independently. If one bot creation fails, it does not affect other bots in the batch. Failed items are included in the `errors` array while successful items are in the `data` array.
    
    Returns 201 with partial success response. All items may succeed, all may fail, or any combination. Always check both `data` and `errors` arrays.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/batch","method":"post"}]} />


---

## Create multiple scheduled bots

### Source: ./content/docs/api-v2/reference/bots/batchCreateScheduledBots.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Create multiple scheduled bots in a single request with partial success support.
    
    Processes each scheduled bot creation request sequentially. Each item is validated and processed independently. Token reservation and daily bot cap checks are NOT performed at creation time - they are performed when each bot actually joins the meeting.
    
    **Processing Order:** Items are processed in the order they appear in the request array. Each item goes through validation: platform detection, transcription key availability, and join time validation. Unlike immediate bot creation, daily bot cap and token availability are not checked at creation time.
    
    **Partial Success:** The response always has `success: true`, even if all items fail. Check the `errors` array to identify failed items. The `data` array contains successfully scheduled bots with their `bot_id` and preserved `extra` metadata.
    
    **Join Time Validation:** Each scheduled bot's `join_at` time must be in the future (at least 1 minute ahead). If a join time is invalid, that item will fail with a validation error, but other items will continue processing.
    
    **Error Scenarios:** 
    - Validation errors: Invalid join time, invalid meeting URL, invalid configuration
    - Platform detection failures: `INVALID_MEETING_PLATFORM`
    - System failures: `BOT_CREATE_FAILED`
    
    **Note:** Daily bot cap and token availability are checked when each bot joins, not at creation time. If these checks fail at join time, the bot will transition to `failed` status and send a failure webhook.
    
    Returns 201 with partial success response. All items may succeed, all may fail, or any combination.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/scheduled/batch","method":"post"}]} />


---

## Create a bot

### Source: ./content/docs/api-v2/reference/bots/createBot.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Create a bot to join a meeting immediately.
    
    The bot will automatically join the meeting, request recording permissions, and start recording once accepted. You can provide a bot-specific callback URL to receive bot.completed and bot.failed events for this bot (in addition to your account's webhooks). The bot will send webhook events for status changes, completion, and failures.
    
    Returns a `bot_id` (UUID) that you can use to track status, retrieve meeting data, and manage the bot. The bot will be queued immediately and join the meeting as soon as possible (may take up to 2 minutes depending on availability of bot slots).
    
    **Token Reservation:** 0.5 tokens are reserved immediately upon creation. These tokens will be consumed based on the bot's duration and outcome. If the bot fails due to user-responsible errors (`BOT_NOT_ACCEPTED`, `TIMEOUT_WAITING_TO_START`), recording tokens will be charged based on the time spent in the waiting room.
    
    **Deduplication:** By default, multiple bots can join the same meeting URL. Set `allow_multiple_bots: false` to prevent duplicate bots within 5 minutes. The deduplication check expires after 5 minutes, allowing a new bot to join the same meeting URL after that period.
    
    **Rate Limits:** Subject to your API key's rate limits and your team's daily bot cap. The daily bot cap is checked before token reservation. If the cap is reached, the request will fail with a 429 status code.
    
    **Error Scenarios:**
    - `402 Payment Required`: Insufficient tokens available
    - `409 Conflict`: Bot already exists for the same meeting URL (when `allow_multiple_bots` is false)
    - `429 Too Many Requests`: Daily bot cap reached or rate limit exceeded

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots","method":"post"}]} />


---

## Create scheduled bot

### Source: ./content/docs/api-v2/reference/bots/createScheduledBot.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Schedule a bot to join a meeting at a specific time in the future.
    
    The bot will automatically join the meeting at the specified `join_at` time (ISO 8601 timestamp). You can provide a callback URL to receive events for this bot. The bot configuration is stored immediately, but token reservation and daily bot cap checks are performed when the bot actually joins the meeting.
    
    **Scheduling:** The `join_at` timestamp must be in the future (at least 1 minute ahead). The bot will automatically attempt to join the meeting at the specified time. There may be a small processing delay (typically less than a minute).
    
    **Token Reservation:** Tokens are NOT reserved at creation time. Token availability and daily bot cap are checked when the bot actually joins the meeting. If tokens are insufficient or the daily cap is reached at join time, the bot will fail with an appropriate error and transition to `failed` status.
    
    **Deduplication:** Deduplication is checked when the bot joins, not at creation time. This means you can schedule multiple bots for the same meeting URL, but only one will successfully join (unless `allow_multiple_bots` is true).
    
    **Status:** The scheduled bot starts in `scheduled` status and transitions to `completed` when the bot instance is created and queued to join. If the bot fails to join, it transitions to `failed` status.
    
    **Updates and Deletions:** Scheduled bots can be updated or deleted as long as they are in `scheduled` status and the join time is at least 4 minutes in the future. This ensures the bot can be modified before it starts processing.
    
    Returns a `bot_id` (UUID) that you can use to track and manage the scheduled bot. This UUID will be reused as the bot's UUID when it actually joins.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/scheduled","method":"post"}]} />


---

## Delete bot data

### Source: ./content/docs/api-v2/reference/bots/deleteBotData.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Permanently delete all bot data including recordings, transcripts, summaries, and screenshots.
    
    This operation is irreversible. All artifacts (video, audio, transcription, diarization, screenshots) will be permanently deleted. Optionally delete transcription data from the transcription provider as well using the `delete_transcription` query parameter.
    
    **Data Deletion:** 
    - All artifacts (video, audio, transcription, diarization, screenshots) are permanently deleted
    - The `artifacts_deleted` field is set to `true`
    - Artifact URLs will return `null` in subsequent API calls
    - Bot metadata remains accessible but all associated data is removed
    
    **Transcription Provider Deletion:** If `delete_transcription=true` is provided, the transcription data will also be deleted from the transcription provider. This requires the bot to have transcription enabled and a transcription provider configured. If the bot uses BYOK transcription, you must have access to the transcription provider API key.
    
    **Irreversible Operation:** Once data is deleted, it cannot be recovered. Make sure you have downloaded or backed up any data you need before calling this endpoint.
    
    **Status Requirements:** The bot must be in `completed` or `failed` status. Bots that are still in progress (queued, joining, in_call_recording, transcribing) cannot have their data deleted. If the bot is in an invalid state, the request will fail with a 409 Conflict status.
    
    Returns 404 if the bot is not found, or 409 if the bot's status does not allow this operation.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/{bot_id}/delete-data","method":"delete"}]} />


---

## Delete scheduled bot

### Source: ./content/docs/api-v2/reference/bots/deleteScheduledBot.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Cancel and delete a scheduled bot. The behavior depends on where the bot is in its lifecycle when you call this:

    - **Not yet launched** — the schedule is cancelled and the bot never starts.
    - **Launched but not yet in the meeting** — the bot exits before joining, with an `EXITING_MEETING_BEFORE_RECORD` error code. No meeting content is captured.
    - **Already in the meeting** — the bot leaves. Whatever it captured up to that point (audio, video, transcript, diarization) is still processed and delivered as usual.

    **Status Requirements:** The scheduled bot must not already be `cancelled`, `completed`, or `failed`. If the bot is in a terminal state, the request will fail with a 409 Conflict status.

    **Irreversible Operation:** Once a scheduled bot is cancelled, it cannot be recovered.

    **Token Impact:** Tokens are not reserved for scheduled bots, so cancelling before the bot joins a meeting has no impact on your token balance. If the bot had already started recording, tokens are consumed only for the recorded portion — you won't be charged for time after the cancellation.

    Returns 404 if the scheduled bot is not found, or 409 if the bot's status does not allow deletion.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/scheduled/{bot_id}","method":"delete"}]} />


---

## Get bot details

### Source: ./content/docs/api-v2/reference/bots/getBotDetails.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Get comprehensive information about a specific bot.
    
    Returns detailed bot information including current status, configuration, meeting metadata, and presigned URLs for all artifacts (video, audio, transcription, diarization). Artifact URLs are valid for 4 hours from the time of request. Returns `null` for artifacts if the bot's data has been deleted.
    
    **Artifact URLs:** All artifact URLs (video, audio, transcription, diarization) are presigned URLs that expire after 4 hours. If the bot's data has been deleted (via the delete-data endpoint or data retention policy), these fields will be `null`. The `artifacts_deleted` field indicates whether the bot's data has been permanently removed.
    
    **Status Information:** The response includes the bot's current status (`status` field) and timestamps for key events (joined_at, exited_at, created_at). If the bot failed, the response includes `error_code` and `error_message` fields with details about what went wrong.
    
    **Meeting Metadata:** Includes meeting platform, meeting URL, participants list, speakers list, and meeting duration (if available). Some metadata may be `null` if the bot failed before joining or if the information is not available.
    
    **Transcription Information:** If transcription was enabled, the response includes transcription provider, transcription IDs (for BYOK providers), and URLs to raw and processed transcription files.
    
    Returns 404 if the bot is not found or does not belong to your team.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/{bot_id}","method":"get"}]} />


---

## Get bot screenshots

### Source: ./content/docs/api-v2/reference/bots/getBotScreenshots.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieve a paginated list of screenshot URLs captured during the meeting.
    
    Screenshots are taken periodically during the meeting and can be used to visualize meeting content. Each screenshot is a presigned URL valid for 4 hours.
    
    **Screenshot Availability:** 
    - Screenshots are only available for Google Meet and Microsoft Teams bots
    - Screenshots are only available for bots with `recording_mode` set to `speaker_view` or `gallery_view`
    - Audio-only recordings do not have screenshots
    - Screenshots are captured at regular intervals during the meeting
    
    **Pagination:** Uses cursor-based pagination. Provide a `cursor` query parameter to fetch the next page. The `limit` parameter controls how many screenshots are returned per page (default: 50, max: 100).
    
    **URL Expiration:** All screenshot URLs are presigned URLs that expire after 4 hours. If the bot's data has been deleted, this endpoint will return an empty list.
    
    Returns 404 if the bot is not found or does not belong to your team.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/{bot_id}/screenshots","method":"get"}]} />


---

## Get bot status

### Source: ./content/docs/api-v2/reference/bots/getBotStatus.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Get the current status of a bot, including the latest status code, transcription status, and timestamp.
    
    Useful for polling bot state without fetching the full bot details. Returns lightweight status information including the current status code, transcription status, and when the status was last updated (`updated_at`).
    
    **Response Fields:**
    - `bot_id`: The UUID of the bot
    - `status`: The current bot status (queued, joining, in_call_recording, retrying, transcribing, completed, failed)
    - `transcription_status`: The current transcription status (not-applicable, not-started, queued, processing, done, error)
    - `updated_at`: ISO 8601 timestamp when the status was last updated
    
    **Transcription Status:** The transcription status is fetched in real-time from the transcription provider if transcription is enabled. This allows you to track transcription progress separately from the bot's overall status.
    
    **Polling Considerations:** 
    - **Not Recommended for Active Monitoring:** Due to the nature of meetings running for extended periods (often hours), frequent polling is not recommended. Instead, use `callback_config` when creating bots or configure webhooks at the account level to receive real-time status updates.
    - **Reconciliation Use Case:** This endpoint is better suited for reconciliation purposes (e.g., checking bot status after a webhook delivery failure or verifying final state).
    - **If Polling is Necessary:** If you must poll, use a judicious interval (e.g., every 5-10 minutes) and implement exponential backoff to avoid rate limits. Consider the meeting duration when determining polling frequency.
    
    Returns 404 if the bot is not found or does not belong to your team.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/{bot_id}/status","method":"get"}]} />


---

## Get scheduled bot details

### Source: ./content/docs/api-v2/reference/bots/getScheduledBotDetails.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieve detailed information about a specific scheduled bot.
    
    Returns the scheduled bot's configuration, scheduled join time, current status, and associated bot instance (if the bot has already joined). Includes all the same configuration options as immediate bot creation.
    
    **Status Information:** The response includes the scheduled bot's current status (`scheduled`, `completed`, or `failed`) and when the status was last updated. If the bot has joined, the response includes a link to the actual bot instance.
    
    **Scheduled Join Time:** The `join_at` field contains the ISO 8601 timestamp when the bot is scheduled to join the meeting.
    
    **Bot Instance:** If the scheduled bot has transitioned to `completed` status, the bot instance has been created and is queued to join. You can use the `bot_id` (which will be reused as the bot's UUID when it joins) to query the bot's status and retrieve meeting data once it has joined.
    
    **Updates and Deletions:** If the bot is in `scheduled` status and the join time is at least 4 minutes in the future, you can update or delete the scheduled bot. This ensures the bot can be modified before it starts processing.
    
    Returns 404 if the scheduled bot is not found or does not belong to your team.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/scheduled/{bot_id}","method":"get"}]} />


---

## Bots

Create, list, and manage meeting bots and scheduled bots.

### Source: ./content/docs/api-v2/reference/bots/index.mdx


Endpoints for sending bots into meetings, controlling recordings, and managing scheduled bots.


---

## Leave meeting

### Source: ./content/docs/api-v2/reference/bots/leaveBot.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Instruct a bot to leave the meeting immediately.

    The bot will stop recording and processing, then exit the meeting. Works for bots in any active state: `queued`, `joining_call`, `in_waiting_room`, `in_call_not_recording`, `in_call_recording`, `recording_paused`, or `recording_resumed`. Also works for scheduled bots that haven't spawned yet — the scheduled bot will be cancelled atomically. The bot will send a final webhook event when it leaves.

    **Status Requirements:** The bot must be in an active (non-terminal) state. Bots that have already `completed` or `failed` cannot be left via this endpoint. If the bot is in an invalid state, the request will fail with a 409 Conflict status.

    **Pre-Recording Stops:** If the bot hasn't started recording yet (e.g., still `queued` or in the waiting room), it will exit with an `EXITING_MEETING_BEFORE_RECORD` error code. No tokens are consumed for pre-recording stops.

    **Token Consumption:** When a bot that was recording is manually left, tokens are consumed based on the duration from when recording started to when the bot left. The bot will transition to `completed` status and send a completion webhook.
    Returns 404 if the bot is not found, or 409 if the bot's status does not allow this operation.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/{bot_id}/leave","method":"post"}]} />


---

## List bots

### Source: ./content/docs/api-v2/reference/bots/listBots.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

List all bots for your team with pagination support.
    
    Filter by status (queued, joining, in_call_recording, retrying, transcribing, completed, failed), meeting platform (zoom, meet, teams), and date range. Results are ordered by creation date (newest first). Use cursor-based pagination for efficient navigation through large result sets.
    
    **Pagination:** Uses cursor-based pagination. Provide a `cursor` query parameter to fetch the next page. The response includes a `next_cursor` if more results are available. The `limit` parameter controls how many results are returned per page (default: 50, max: 250).
    
    **Filtering:** 
    - `status`: Filter by bot status (comma-separated for multiple statuses)
    - `platform`: Filter by meeting platform (zoom, meet, teams)
    - `created_after`: ISO 8601 timestamp - only return bots created after this time
    - `created_before`: ISO 8601 timestamp - only return bots created before this time
    
    **Date Range:** The `created_after` and `created_before` filters use ISO 8601 timestamps. Results are limited to bots created within the last 90 days by default.
    
    Returns a paginated list of bots with metadata including bot ID, status, meeting platform, creation time, and basic configuration.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots","method":"get"}]} />


---

## List scheduled bots

### Source: ./content/docs/api-v2/reference/bots/listScheduledBots.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieve a paginated list of scheduled bots.
    
    Supports filtering by status (`scheduled`, `completed`, `failed`) and date range. Results are ordered by scheduled join time (earliest first). Use cursor-based pagination for efficient navigation.
    
    **Pagination:** Uses cursor-based pagination. Provide a `cursor` query parameter to fetch the next page. The `limit` parameter controls how many results are returned per page (default: 20, max: 100).
    
    **Filtering:**
    - `status`: Filter by scheduled bot status (comma-separated for multiple statuses)
    - `scheduled_after`: ISO 8601 timestamp - only return bots scheduled to join after this time
    - `scheduled_before`: ISO 8601 timestamp - only return bots scheduled to join before this time
    - `bot_id`, `bot_name`, `meeting_url`: case-insensitive partial match
    - `meeting_platform`: comma-separated list of `zoom`, `meet`, `teams`
    - `extra`: filter by values in the `extra` JSON payload using `key:value` syntax (comma-separated for multiple conditions, e.g. `extra=customer_id:12345,project:sales`). Values match exactly (case-sensitive); scheduled bots without the key are excluded.

    **Status Values:**
    - `scheduled`: Bot is scheduled but has not yet joined
    - `completed`: Bot instance was created and queued to join (bot may still be joining)
    - `failed`: Bot failed to join (token issues, daily cap, etc.)
    
    Returns a paginated list of scheduled bots with metadata including bot ID, scheduled join time, status, and basic configuration.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/scheduled","method":"get"}]} />


---

## Pause recording

### Source: ./content/docs/api-v2/reference/bots/pauseBotRecording.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Pause the bot's recording during a meeting.

    The bot stays in the meeting, but the paused portion is excluded from the final recording, transcript, and diarization. If you have streaming output enabled, the stream is paused too and no audio is forwarded until you resume.

    **Status Requirements:** The bot must be actively recording (`in_call_recording`, or `recording_resumed` if it was previously paused). Bots that are already paused, still joining, or have finished will fail with a 409 Conflict.

    **Chat Message:** Optionally include `chat_message` in the body to post a message in the meeting chat when pausing — e.g., "Recording has been paused".

    **Pairing:** Use `POST /bots/{bot_id}/resume-recording` to continue. You can pause and resume as many times as needed within a single meeting.

    Returns 404 if the bot is not found, or 409 if the bot's status does not allow this operation.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/{bot_id}/pause-recording","method":"post"}]} />


---

## Resend final webhook

### Source: ./content/docs/api-v2/reference/bots/resendFinalWebhook.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Resend the final webhook (completed or failed) for a bot.
    
    Useful if the webhook delivery failed or you need to reprocess the webhook event. The webhook will be sent to all configured webhook endpoints for your account.
    
    **Webhook Delivery:** The webhook will be sent to all configured webhook endpoints for your account. The webhook payload will be identical to the original final webhook (either `bot.completed` or `bot.failed` event).
    
    **Status Requirements:** The bot must be in `completed` or `failed` status. Bots that are still in progress cannot have their final webhook resent. If the bot is in an invalid state, the request will fail with a 409 Conflict status.
    
    **Use Cases:** 
    - Webhook delivery failed due to network issues
    - Webhook endpoint was temporarily unavailable
    - Need to reprocess a webhook event
    - Testing webhook integration
    
    **Idempotency:** This operation is idempotent. You can call it multiple times, and it will resend the webhook each time. There is no limit on how many times you can resend a webhook.
    
    Returns 404 if the bot is not found, or 409 if the bot's status does not allow this operation.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/{bot_id}/resend-webhook","method":"post"}]} />


---

## Resume recording

### Source: ./content/docs/api-v2/reference/bots/resumeBotRecording.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Resume a bot's recording after it was paused.

    Meeting content from this point onward is captured again and included in the final recording, transcript, and diarization. If you have streaming output enabled, audio resumes flowing. Timestamps in the final artifacts are continuous across the pause — the paused gap is collapsed, not represented as silence.

    **Status Requirements:** The bot must be in `recording_paused` status. Bots that are already recording, still joining, or have finished will fail with a 409 Conflict.

    **Chat Message:** Optionally include `chat_message` in the body to post a message in the meeting chat when resuming — e.g., "Recording has resumed".

    **Pairing:** Follows a prior `POST /bots/{bot_id}/pause-recording`. You can pause and resume multiple times within a single meeting.

    Returns 404 if the bot is not found, or 409 if the bot's status does not allow this operation.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/{bot_id}/resume-recording","method":"post"}]} />


---

## Retranscribe bot

### Source: ./content/docs/api-v2/reference/bots/retranscribeBot.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retry transcription for a bot that has audio recordings. Optionally override the transcription provider.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/{bot_id}/retranscribe","method":"post"}]} />


---

## Retry callback

### Source: ./content/docs/api-v2/reference/bots/retryCallback.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retry sending the transcription callback for a bot.
    
    You can override the callback configuration (URL, method, secret) if needed. Only works for bots that have completed or failed. The callback will be sent to the provided URL (or the bot's original callback URL if not overridden).
    
    **Callback Configuration:** You can override the callback URL, HTTP method (POST or PUT), and secret in the request body. If not provided, the bot's original callback configuration will be used. The secret will be included in the `x-mb-secret` header for validation.
    
    **Status Requirements:** The bot must be in `completed` or `failed` status and must have had transcription enabled. Bots without transcription or bots that are still in progress cannot have their callback retried. If the bot is in an invalid state, the request will fail with a 409 Conflict status.
    
    **Callback Payload:** The callback payload will be identical to the original callback (either `bot.completed` or `bot.failed` event with transcription data). The payload format matches the webhook format.
    
    **Use Cases:**
    - Callback delivery failed due to network issues
    - Callback endpoint was temporarily unavailable
    - Need to send callback to a different endpoint
    - Testing callback integration
    
    **Idempotency:** This operation is idempotent. You can call it multiple times with the same or different configurations.
    
    Returns 404 if the bot is not found, or 409 if the bot's status does not allow this operation or if no callback was configured.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/{bot_id}/retry-callback","method":"post"}]} />


---

## Send chat message

### Source: ./content/docs/api-v2/reference/bots/sendChatMessage.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Send a chat message to the meeting through the bot.

    The message will be sent as the bot in the meeting's chat. The bot must be actively in the meeting to send messages. Messages are limited to 4096 characters and cannot be empty or whitespace-only.

    **Status Requirements:** The bot must be in one of the following statuses: `in_call_not_recording`, `in_call_recording`, `recording_paused`, or `recording_resumed`. If the bot is in any other state (e.g., `queued`, `joining_call`, `in_waiting_room`, `completed`, `failed`), the request will fail with a 409 Conflict error (`FST_ERR_BOT_STATUS`).

    **Chat Disabled:** Some meetings have chat disabled by the host or meeting policy. If the bot attempts to send a message in a meeting where chat is not available, the request will fail with a 422 Unprocessable Entity error (`FST_ERR_CHAT_DISABLED`). This is determined at runtime by the meeting platform and cannot be known in advance. Chat disabled detection works for Zoom and Microsoft Teams meetings. For Google Meet, message delivery is best-effort — the bot may report success even if the host has restricted chat permissions for external participants.

    **Message Delivery:** The message is forwarded to the bot process which sends it through the meeting platform's chat API (Google Meet, Microsoft Teams, or Zoom). Delivery is best-effort — if the bot process is unreachable or the platform rejects the message, the request will fail with a 500 Internal Server Error (`FST_ERR_SEND_CHAT_MESSAGE_FAILED`).

    **Message Persistence:** Successfully sent messages are included in the `chat_messages` artifact alongside received messages when the bot completes. Bot-sent messages have `sender_id: null` and the bot's display name as `sender_name`.

    Returns 404 if the bot is not found, 409 if the bot's status does not allow this operation, or 422 if chat is disabled in the meeting.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/{bot_id}/send-chat-message","method":"post"}]} />


---

## Update bot configuration

### Source: ./content/docs/api-v2/reference/bots/updateBotConfig.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Update bot configuration (currently only supports updating the extra parameter).
    
    Allows updating the `extra` metadata even while the bot is running. The updated extra will be reflected in subsequent webhooks and API responses. This is useful when your system evolves and you need to attach additional tracking information to a bot after it has started.
    
    **Merge Behavior:** The `extra` parameter performs a shallow merge with the existing extra object:
    - New keys are added to the existing extra object
    - Existing keys are overwritten with new values
    - Keys not included in the update request remain unchanged
    - Pass `null` to clear all extra data
    
    **Example Merge:**
    - Current extra: `{ "customer_id": "123", "session_id": "abc" }`
    - Update with: `{ "session_id": "xyz", "order_id": "456" }`
    - Result: `{ "customer_id": "123", "session_id": "xyz", "order_id": "456" }`
    
    **Webhook Behavior:** After updating extra, all future webhooks (including status updates) will use the new value from the database. The updated extra is fetched in real-time for each webhook, ensuring consistency.
    
    **Works for Any Bot Status:** You can update extra for bots in any status (queued, recording, completed, failed). This allows you to add correlation metadata even after a bot has finished.
    
    **Use Cases:**
    - Add tracking IDs after bot creation
    - Update correlation metadata when your system state changes
    - Fix incorrect tracking information
    - Add additional context for completed bots
    
    Returns 404 if the bot is not found or does not belong to your team.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/{bot_id}/update-config","method":"patch"}]} />


---

## Update scheduled bot

### Source: ./content/docs/api-v2/reference/bots/updateScheduledBot.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Update a scheduled bot's configuration or scheduled join time.
    
    The bot must be in `scheduled` status and the join time must be at least 4 minutes in the future. This ensures the bot can be updated before it starts processing.
    
    **Updateable Fields:** You can update any configuration field (bot name, image, recording mode, transcription settings, etc.) and the scheduled join time (`join_at`). All fields are optional - only provided fields will be updated.
    
    **Join Time Requirements:** 
    - The new `join_at` time must be in the future
    - The bot must be in `scheduled` status
    - The join time must be at least 4 minutes in the future (lock window)
    - If the join time is too close, the request will fail with 409 Conflict
    
    **Status Requirements:** The bot must be in `scheduled` status. Bots that have already joined (`completed`) or failed (`failed`) cannot be updated. If the bot is in an invalid state, the request will fail with a 409 Conflict status.
    
    **Validation:** All updated fields are validated using the same rules as bot creation. Invalid configurations will result in a 400 Bad Request error.
    
    Returns 404 if the scheduled bot is not found, or 409 if the bot's status does not allow update or the join time is too close.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/bots/scheduled/{bot_id}","method":"patch"}]} />


---

## Schedule bot for calendar event

### Source: ./content/docs/api-v2/reference/calendars/createCalendarBot.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Schedule a bot to automatically join a calendar event.
    
    You can schedule for all occurrences of a recurring event or specific event instances. The bot will use the meeting URL from the event. Returns partial success if some events fail to schedule (e.g., if a bot is already scheduled or if the event doesn't have a meeting URL).
    
    **Scheduling Options:**
    - `series_id`: Schedule for all occurrences of a recurring series
    - `event_id`: Schedule for a specific event instance
    - `all_occurrences`: Schedule for all future occurrences (for recurring events)
    
    **Meeting URL Requirement:** The event must have a meeting URL. If the event doesn't have a meeting URL, the scheduling will fail for that event. The meeting platform is automatically detected from the URL.
    
    **Bot Configuration:** You can provide bot configuration (name, image, recording mode, transcription settings, etc.) that will be used for all scheduled bots. The configuration applies to all events you're scheduling for.
    
    **Partial Success:** If you're scheduling for multiple events (e.g., all occurrences of a series), some events may fail to schedule (e.g., if a bot is already scheduled). The response includes information about which events succeeded and which failed.
    
    **Token Reservation:** Tokens are NOT reserved at scheduling time. Token availability and daily bot cap are checked when each bot actually joins the meeting. If tokens are insufficient or the daily cap is reached at join time, the bot will fail with an appropriate error.
    
    **Status:** The calendar bot schedule starts in `scheduled` status and transitions to `completed` when the bot instance is created and queued to join. If the bot fails to join, it transitions to `failed` status.
    
    Returns 201 with scheduling results. Returns 404 if the event series or event is not found.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/calendars/{calendar_id}/bots","method":"post"}]} />


---

## Create calendar connection

### Source: ./content/docs/api-v2/reference/calendars/createCalendarConnection.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Connect a Google or Microsoft calendar to your account.
    
    The connection will automatically sync events and create push subscriptions for real-time updates. You must provide your own OAuth credentials (client ID, secret, refresh token). Once connected, the calendar will be synced immediately, and webhook subscriptions will be created for real-time event updates.
    
    **OAuth Credentials:** You must provide valid OAuth credentials for the calendar provider. The endpoint will validate the credentials by attempting to refresh the access token. If the refresh token is invalid or expired, the request will fail with 401 Unauthorized.
    
    **Initial Sync:** After creating the connection, an initial sync is performed automatically. This fetches all events from the calendar provider. The sync may take a few minutes for calendars with many events.
    
    **Push Subscriptions:** A push subscription is created automatically for real-time event updates. The subscription will send webhooks when events are created, updated, or cancelled.
    
    **Calendar Limits:** There may be limits on the number of calendar connections per team. If the limit is exceeded, the request will fail with 429 Status Code.
    
    **Duplicate Connections:** If a connection already exists for the same calendar ID and team, the request will fail with 409 Conflict. You can update an existing connection using the PATCH endpoint instead.
    
    Returns 201 with the newly created calendar connection. Returns 401 if OAuth token refresh failed, 429 if the calendar connection limit is exceeded, or 409 if the connection already exists.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/calendars","method":"post"}]} />


---

## Cancel calendar bot

### Source: ./content/docs/api-v2/reference/calendars/deleteCalendarBot.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Cancel one or more scheduled calendar bots. You can target a single event or all occurrences in a series using `series_id`, `all_occurrences`, and `event_id` in the request body. The behavior for each targeted bot depends on where it is in its lifecycle:

    - **Not yet launched** — the schedule is cancelled and the bot never starts.
    - **Launched but not yet in the meeting** — the bot exits before joining, with an `EXITING_MEETING_BEFORE_RECORD` error code.
    - **Already in the meeting** — the bot leaves. Whatever it captured up to that point is still processed and delivered as usual. This case applies to single-event deletes and to series deletes for recent occurrences; see "Series deletes" below.

    **Cancellation Targets:**
    - `event_id`: Cancel bot for a specific event instance
    - `series_id`: Cancel bots for all occurrences of a series
    - `all_occurrences`: Cancel all future occurrences (for recurring events)

    **Series deletes:** When `all_occurrences` is true, occurrences whose scheduled time is already well in the past are silently skipped — their bots would have long since finished. Recent and future occurrences are cancelled normally. For single-event deletes (`event_id`, no `all_occurrences`), no time filter is applied — whatever state the bot is in, the stop request is delivered.

    **Partial Cancellation:** If cancelling multiple bots (e.g., all occurrences of a series), some bots may fail to cancel. The response includes which bots were cancelled and which failed.

    **Irreversible Operation:** Once a calendar bot is cancelled, it cannot be recovered.

    **Token Impact:** Tokens are not reserved for calendar bots, so cancelling before a bot joins a meeting has no impact on your token balance. If a bot had already started recording, tokens are consumed only for the recorded portion — you won't be charged for time after the cancellation.

    Returns 200 with cancellation results. Returns 404 if the event or calendar bot schedule is not found.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/calendars/{calendar_id}/bots","method":"delete"}]} />


---

## Delete calendar connection

### Source: ./content/docs/api-v2/reference/calendars/deleteCalendarConnection.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Disconnect and delete a calendar connection.
    
    This will stop syncing events and remove all associated calendar data (events, event instances, series). The push subscription will be cancelled automatically. This operation is irreversible.
    
    **Data Deletion:** All calendar data associated with this connection will be deleted:
    - Event series and instances
    - Calendar bot schedules
    - Sync history
    
    **Subscription Cancellation:** The push subscription is cancelled automatically when the connection is deleted. You will no longer receive webhooks for this calendar.
    
    **Irreversible Operation:** Once a calendar connection is deleted, it cannot be recovered. All associated data is permanently removed. If you need to reconnect the calendar, you must create a new connection.
    
    **Bot Schedules:** If there are active calendar bot schedules for events in this calendar, they will be cancelled when the connection is deleted. Bots that have already joined meetings will continue to function normally.
    
    Returns 200 with confirmation of deletion. Returns 404 if the calendar connection is not found or does not belong to your team.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/calendars/{calendar_id}","method":"delete"}]} />


---

## Get calendar connection details

### Source: ./content/docs/api-v2/reference/calendars/getCalendarDetails.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieve detailed information about a specific calendar connection.
    
    Returns the calendar connection's configuration, sync status, subscription status, and last sync time. Includes information about the OAuth credentials (without exposing sensitive data) and the calendar's metadata.
    
    **Sync Status:** The response includes the last sync time and whether the connection is actively syncing. If the connection has errors, the error information is included.
    
    **Subscription Status:** Includes information about the push subscription, including when it was created and when it expires. Subscriptions expire after a certain period and need to be renewed using the resubscribe endpoint.
    
    **Calendar Metadata:** Includes the calendar's ID, name, platform, and account email. This information is fetched from the calendar provider during the initial sync.
    
    Returns 404 if the calendar connection is not found or does not belong to your team.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/calendars/{calendar_id}","method":"get"}]} />


---

## Get event details

### Source: ./content/docs/api-v2/reference/calendars/getEventDetails.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieve detailed information about a specific calendar event.
    
    Returns comprehensive event information including attendees, meeting URL, meeting platform, status, and whether a bot is scheduled. Returns deleted events as well (use the `include_deleted` parameter or check the `deleted_at` field).
    
    **Event Details:** Includes all event metadata:
    - Title, description, location
    - Start and end times (ISO 8601 timestamps)
    - Status (confirmed, cancelled, tentative)
    - Attendees list
    - Meeting URL (if available)
    - Meeting platform (if detected from URL)
    - Whether it's an all-day event
    - Whether it's an exception to a recurring series
    
    **Bot Scheduling:** The `bot_scheduled` field indicates whether a calendar bot schedule exists for this event. If the event is part of a recurring series, the `series_bot_scheduled` field indicates whether a bot is scheduled for all occurrences.
    
    **Deleted Events:** Deleted events are included in the response. Check the `deleted_at` field to determine if an event has been deleted. Deleted events may still have associated bot schedules if they were scheduled before deletion.
    
    Returns 404 if the event is not found or does not belong to the specified calendar.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/calendars/{calendar_id}/events/{event_id}","method":"get"}]} />


---

## Calendars

Connect calendars, manage events, and schedule calendar bots.

### Source: ./content/docs/api-v2/reference/calendars/index.mdx


Endpoints for calendar connections, event listing, and per-event bot scheduling.


---

## List calendar connections

### Source: ./content/docs/api-v2/reference/calendars/listCalendars.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieve a paginated list of calendar connections.
    
    Supports filtering by calendar platform (google, microsoft) and connection status (active, error, revoked, permission_denied). Results are ordered by creation date (newest first). Use cursor-based pagination for efficient navigation.
    
    **Pagination:** Uses cursor-based pagination. Provide a `cursor` query parameter to fetch the next page. The `limit` parameter controls how many results are returned per page (default: 50, max: 250).
    
    **Filtering:**
    - `platform`: Filter by calendar platform (google, microsoft)
    - `status`: Filter by connection status (active, error, revoked, permission_denied)
    
    **Connection Status:**
    - `active`: Connection is working and syncing events
    - `error`: Connection has errors (OAuth token refresh failed, etc.)
    - `revoked`: OAuth access was revoked by the user
    - `permission_denied`: Insufficient permissions for the OAuth scopes
    
    Returns a paginated list of calendar connections with metadata including calendar ID, platform, account email, status, and last sync time.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/calendars","method":"get"}]} />


---

## List event series

### Source: ./content/docs/api-v2/reference/calendars/listEventSeries.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieve a paginated list of event series (both one-off and recurring events).
    
    Each series includes its associated event instances. Supports filtering by event type (one_off, recurring) and whether series are deleted. Use cursor-based pagination for efficient navigation.
    
    **Pagination:** Uses cursor-based pagination. Provide a `cursor` query parameter to fetch the next page. The `limit` parameter controls how many results are returned per page (default: 50, max: 250).
    
    **Event Types:**
    - `one_off`: Single events (not part of a recurring series)
    - `recurring`: Events that are part of a recurring series
    
    **Series Information:** Each series includes its series ID, event type, whether a bot is scheduled for all occurrences (`series_bot_scheduled`), and an array of event instances. For one-off events, the instances array contains a single instance. For recurring events, it contains all instances that have been synced.
    
    **Filtering:**
    - `event_type`: Filter by event type (one_off, recurring)
    - `include_deleted`: Include deleted series in results (default: false)
    
    **Bot Scheduling:** The `series_bot_scheduled` field indicates whether a calendar bot schedule exists for all occurrences of this series. Individual instances may have different bot scheduling status.
    
    Returns a paginated list of event series with their instances.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/calendars/{calendar_id}/series","method":"get"}]} />


---

## List calendar events

### Source: ./content/docs/api-v2/reference/calendars/listEvents.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieve a paginated list of calendar events.
    
    Supports filtering by date range, status (confirmed, cancelled, tentative), and whether events are deleted. Results include whether a bot is scheduled for each event. Use cursor-based pagination for efficient navigation.
    
    **Pagination:** Uses cursor-based pagination. Provide a `cursor` query parameter to fetch the next page. The `limit` parameter controls how many results are returned per page (default: 50, max: 250).
    
    **Filtering:**
    - `start_after`: ISO 8601 timestamp - only return events starting after this time
    - `start_before`: ISO 8601 timestamp - only return events starting before this time
    - `status`: Filter by event status (confirmed, cancelled, tentative)
    - `include_deleted`: Include deleted events in results (default: false)
    
    **Event Information:** Each event includes its ID, title, start/end times, status, meeting URL (if available), meeting platform (if detected), and whether a bot is scheduled for the event.
    
    **Bot Scheduling:** The `bot_scheduled` field indicates whether a calendar bot schedule exists for this event. This does not mean the bot has joined - it means a bot is scheduled to join when the event starts.
    
    Returns a paginated list of calendar events with metadata.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/calendars/{calendar_id}/events","method":"get"}]} />


---

## List raw calendars (preview before creating connection)

### Source: ./content/docs/api-v2/reference/calendars/listRawCalendars.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Preview available calendars from a Google or Microsoft account before creating a connection.
    
    Requires OAuth credentials (client ID, client secret, refresh token) to authenticate and list calendars. This endpoint does not create a connection - it only lists the calendars that are available for the given OAuth credentials. Useful for allowing users to select which calendars to sync.
    
    **OAuth Credentials:** You must provide valid OAuth credentials for the calendar provider. The endpoint will use the refresh token to obtain an access token and list calendars. If the refresh token is invalid or expired, the request will fail with 401 Unauthorized.
    
    **Calendar Information:** Returns a list of calendars with their IDs, names, descriptions, and whether they are primary calendars. Calendar IDs differ between providers (Google uses email-like IDs, Microsoft uses GUIDs).
    
    **Use Case:** This endpoint is typically called before creating a calendar connection to show users which calendars are available. Users can then select which calendars they want to sync.
    
    Returns 401 if OAuth token refresh failed, or 403 if a Microsoft account license is required.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/calendars/list-raw","method":"post"}]} />


---

## Resubscribe to calendar webhooks

### Source: ./content/docs/api-v2/reference/calendars/resubscribeCalendar.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Renew or recreate the push subscription for a calendar connection.
    
    Useful when subscriptions expire or need to be refreshed. A new subscription will be created and the old one will be cancelled. Subscriptions expire after a certain period (3 days for Microsoft, longer for Google) and need to be renewed periodically.
    
    **Subscription Renewal:** The endpoint creates a new push subscription with the calendar provider. The old subscription is cancelled to prevent duplicate webhooks. The new subscription will send webhooks for all calendar events (created, updated, cancelled).
    
    **Subscription Expiration:** Subscriptions expire automatically after a certain period:
    - Microsoft: 3 days maximum
    - Google: Longer period (varies)
    
    When a subscription expires, you will stop receiving webhook notifications. Use this endpoint to renew the subscription before it expires.
    
    **Use Cases:**
    - Subscription is about to expire
    - Subscription has expired and webhooks stopped working
    - Need to refresh subscription for troubleshooting
    
    **Response:** The response includes information about the new subscription, including when it was created and when it expires.
    
    Returns 200 with subscription information. Returns 401 if OAuth token refresh failed, 403 if permission is denied, or 404 if the calendar connection is not found.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/calendars/{calendar_id}/resubscribe","method":"post"}]} />


---

## Sync calendar events

### Source: ./content/docs/api-v2/reference/calendars/syncCalendar.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Manually trigger a sync of calendar events.
    
    This will fetch all events from the calendar provider and update the calendar data. Events are normally synced automatically via push subscriptions, but you can use this endpoint to force a sync (e.g., after fixing connection errors or when you need immediate updates).
    
    **Sync Process:** The sync process fetches all events from the calendar provider. New events are added, updated events are modified, and cancelled events are marked as deleted. The sync may take a few minutes for calendars with many events.
    
    **Incremental vs Full Sync:** The endpoint performs a full sync, fetching all events from the calendar. Incremental syncs happen automatically via push subscriptions when events are created, updated, or cancelled.
    
    **Use Cases:**
    - Force a sync after fixing connection errors
    - Get immediate updates without waiting for push notifications
    - Recover from missed push notifications
    - Initial sync after creating a connection (though this happens automatically)
    
    **Response:** The response includes information about the sync operation, including how many events were synced. The actual event data is available via the list events endpoint.
    
    Returns 200 with sync results. Returns 401 if OAuth token refresh failed, or 404 if the calendar connection is not found.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/calendars/{calendar_id}/sync","method":"post"}]} />


---

## Update calendar bot

### Source: ./content/docs/api-v2/reference/calendars/updateCalendarBot.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Update one or more calendar bots for a calendar.
    
    You can target a single event or all occurrences in a series using `series_id`, `all_occurrences`, and `event_id` in the request body. The bot must be in `scheduled` status and the join time must be at least 4 minutes in the future.
    
    **Update Targets:**
    - `event_id`: Update bot for a specific event instance
    - `series_id`: Update bots for all occurrences of a series
    - `all_occurrences`: Update all future occurrences (for recurring events)
    
    **Updateable Fields:** You can update any bot configuration field (bot name, image, recording mode, transcription settings, etc.). All fields are optional - only provided fields will be updated.
    
    **Status Requirements:** The bot must be in `scheduled` status. Bots that have already joined (`completed`) or failed (`failed`) cannot be updated. If the bot is in an invalid state, the request will fail with a 409 Conflict status.
    
    **Join Time Requirements:** The join time must be at least 4 minutes in the future. If the join time is too close, the request will fail with 409 Conflict. This ensures the bot can be updated before it starts processing.
    
    **Partial Updates:** If updating multiple bots (e.g., all occurrences of a series), some bots may fail to update (e.g., if they're not in `scheduled` status). The response includes information about which bots were updated and which failed.
    
    Returns 200 with update results. Returns 404 if the event or calendar bot schedule is not found, or 409 if the bot's status does not allow update.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/calendars/{calendar_id}/bots","method":"patch"}]} />


---

## Update calendar connection

### Source: ./content/docs/api-v2/reference/calendars/updateCalendarConnection.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Update a calendar connection with new OAuth credentials.
    
    Useful when refresh tokens expire or credentials need to be rotated. The connection will be validated and a new push subscription will be created. The old subscription will be cancelled automatically.
    
    **OAuth Credentials:** You can update the client ID, client secret, and refresh token. All fields are optional - only provided fields will be updated. The endpoint will validate the new credentials by attempting to refresh the access token.
    
    **Validation:** After updating credentials, the connection is validated by attempting to refresh the access token. If the refresh fails, the connection status is updated to `error` and the request may fail with 401 Unauthorized.
    
    **Subscription Renewal:** A new push subscription is created automatically after updating credentials. The old subscription is cancelled to prevent duplicate webhooks.
    
    **Use Cases:**
    - Refresh token expired and needs to be renewed
    - OAuth credentials rotated for security
    - Fixing connection errors by updating credentials
    
    Returns 200 with the updated calendar connection. Returns 401 if OAuth token refresh failed, 403 if permission is denied, or 404 if the calendar connection is not found.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/calendars/{calendar_id}","method":"patch"}]} />


---

## Completed

Completed payload structure

### Source: ./content/docs/api-v2/reference/callbacks/callbackcompleted.mdx




## Payload Structure

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `data` | object | Yes |  |
| `event` | string | Yes | The webhook event type |
| `extra` | object | null | Yes | Additional metadata provided when creating the bot. This is user-defined data that can be used for correlation or tracking |

## Field Details

- **`data`** (object) **Required**

  Properties:
    - **`audio`** (string (uri) | null) **Required**
      Signed URL to download the audio recording. Valid for 4 hours. Null if audio recording is not available or has been deleted

    - **`bot_id`** (string (uuid)) **Required**
      The UUID of the bot that completed

    - **`data_deleted`** (boolean) **Required**
      Whether the bot's data (artifacts, recordings) has been deleted. True if data has been permanently removed

    - **`diarization`** (string (uri) | null) **Required**
      Signed URL to download the speaker diarization data. Valid for 4 hours. Null if diarization is not available or has been deleted

    - **`duration_seconds`** (integer | null) **Required**

    - **`event_id`** (string (uuid) | null) **Required**
      The UUID of the calendar event associated with this bot. Null for non-calendar bots

    - **`exited_at`** (string (date-time) | null) **Required**
      ISO 8601 timestamp when the bot exited the meeting. Null if exit time is not available

    - **`joined_at`** (string (date-time) | null) **Required**
      ISO 8601 timestamp when the bot joined the meeting. Null if join time is not available

    - **`participants`** (object[]) **Required**
      List of participants who joined the meeting with their names and metadata. Empty array if participant information is not available

    - **`raw_transcription`** (string (uri) | null) **Required**
      Signed URL to download the raw transcription file. Valid for 4 hours. Null if raw transcription is not available or has been deleted

    - **`sent_at`** (string (date-time)) **Required**
      ISO 8601 timestamp when this webhook was sent

    - **`speakers`** (object[]) **Required**
      List of speakers detected in the meeting with their names and metadata. Empty array if speaker information is not available

    - **`transcription`** (string (uri) | null) **Required**
      Signed URL to download the processed transcription file. Valid for 4 hours. Null if transcription is not available or has been deleted

    - **`transcription_ids`** (string[] | null) **Required**
      Array of transcription job IDs from the transcription provider. Null if transcription was not enabled or if IDs are not available

    - **`transcription_provider`** (string | null) **Required**
      The transcription provider used (e.g., 'gladia', 'deepgram', 'assemblyai'). Null if transcription was not enabled or if provider information is not available

    - **`video`** (string (uri) | null) **Required**
      Signed URL to download the video recording. Valid for 4 hours. Null if video recording is not available or has been deleted


- **`event`** (string) **Required**
  The webhook event type

- **`extra`** (object | null) **Required**
  Additional metadata provided when creating the bot. This is user-defined data that can be used for correlation or tracking


## Example

```json
{
  "data": {
    "audio": null,
    "bot_id": "examplebot_id",
    "data_deleted": true,
    "diarization": null,
    "duration_seconds": null,
    "event_id": null,
    "exited_at": null,
    "joined_at": null,
    "participants": [],
    "raw_transcription": null,
    "sent_at": "examplesent_at",
    "speakers": [],
    "transcription": null,
    "transcription_ids": [],
    "transcription_provider": null,
    "video": null
  },
  "event": "exampleevent",
  "extra": null
}
```


---

## Failed

Failed payload structure

### Source: ./content/docs/api-v2/reference/callbacks/callbackfailed.mdx




## Payload Structure

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `data` | object | Yes |  |
| `event` | string | Yes | The webhook event type |
| `extra` | object | null | Yes | Additional metadata provided when creating the bot. This is user-defined data that can be used for correlation or tracking |

## Field Details

- **`data`** (object) **Required**

  Properties:
    - **`bot_id`** (string (uuid)) **Required**
      The UUID of the bot that failed

    - **`error_code`** (string) **Required**
      Machine-readable error code for programmatic handling. Common codes include 'MEETING_NOT_FOUND', 'MEETING_ENDED', 'BOT_CRASHED', etc.

    - **`error_message`** (string) **Required**
      Human-readable error message describing why the bot failed

    - **`event_id`** (string (uuid) | null) **Required**
      The UUID of the calendar event associated with this bot. Null for non-calendar bots

    - **`sent_at`** (string (date-time)) **Required**
      ISO 8601 timestamp when this webhook was sent


- **`event`** (string) **Required**
  The webhook event type

- **`extra`** (object | null) **Required**
  Additional metadata provided when creating the bot. This is user-defined data that can be used for correlation or tracking


## Example

```json
{
  "data": {
    "bot_id": "examplebot_id",
    "error_code": "exampleerror_code",
    "error_message": "exampleerror_message",
    "event_id": null,
    "sent_at": "examplesent_at"
  },
  "event": "exampleevent",
  "extra": null
}
```


---

## Callback Payloads

Reference documentation for all callback payload structures

### Source: ./content/docs/api-v2/reference/callbacks/index.mdx


This section contains reference documentation for all callback payload structures sent by Meeting BaaS v2.

## Callbacks

- [Callback Completed](/docs/api-v2/reference/callbacks/callbackcompleted)
- [Callback Failed](/docs/api-v2/reference/callbacks/callbackfailed)


---

## Create a meet login

### Source: ./content/docs/api-v2/reference/meet-logins/createMeetLogin.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Create a meet login — a Google Workspace user identity attached to a parent meet workspace.

    Each login represents one Workspace user that bots can sign in as. Many logins can share one workspace; they all use the workspace's cert/key for SAML signing. Round-robin assignment picks the least-loaded active login when a bot is dispatched with `meet_config.email_group` (or `credential_id`) on `POST /v2/bots`.

    **Prerequisites:** Create a meet workspace first via `POST /v2/meet-workspaces`. Pass the returned `workspace_id` here.

    **Domain Validation:** The `email` must belong to the parent workspace's domain (or a subdomain). For example, if the workspace domain is `bots.acme.com`, valid emails include `bot1@bots.acme.com` or `bot2@dev.bots.acme.com`, but not `bot1@acme.com`. The same rule applies to `email_group` if provided.

    **Email Group (optional but encouraged):** Set `email_group` to a Google Group address that contains the bot users as members. When you put this group address on a calendar invite, the assigned bot lands in Meet's verified queue and bypasses the waiting room. Logins sharing the same `email_group` form one round-robin pool.

    **Per-team Uniqueness on Email:** Each `email` may exist at most once per team. Attempting to register the same email twice returns 409.

    **Concurrency:** Each login supports up to 20 concurrent SSO sessions by default. When a login hits its capacity, the round-robin assigner skips it and tries the next one in the pool. If all logins in a pool are saturated, the bot creation fails with `MEET_LOGIN_UNAVAILABLE` (or falls back to anonymous, depending on your `fallback` setting).

    **Error Scenarios:**
    - `404 Not Found`: `workspace_id` is unknown or does not belong to your team.
    - `409 Conflict`: A login for this `email` already exists.
    - `422 Unprocessable Entity`: `email` or `email_group` domain does not match the parent workspace's domain.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/meet-logins","method":"post"}]} />


---

## Delete a meet login

### Source: ./content/docs/api-v2/reference/meet-logins/deleteMeetLogin.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Delete a meet login.

    **In-Use Guard:** If the login has any active SSO sessions in flight (`active_session_count > 0`), the request returns 409. Wait for the bots to finish or stop them first.

    **Bot Reference Handling:** Bots that previously used this login keep their reference in `assigned_meet_login_id` until the row is deleted, then the FK is set to `null` (no historical bot record is removed; just the link).

    Returns 404 if the login is not found or does not belong to your team.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/meet-logins/{credential_id}","method":"delete"}]} />


---

## Get a meet login

### Source: ./content/docs/api-v2/reference/meet-logins/getMeetLogin.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Get full details for a single meet login.

    Returns the same fields as the list endpoint, scoped to one login.

    Returns 404 if the login is not found or does not belong to your team.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/meet-logins/{credential_id}","method":"get"}]} />


---

## Get current login pool utilization

### Source: ./content/docs/api-v2/reference/meet-logins/getMeetLoginUtilization.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Get current concurrency utilization for your team's meet login pool.

    Returns aggregate metrics across all your active logins:

    - `logins_total`: total number of logins for your team
    - `logins_active`: number with `state: "active"`
    - `logins_invalid`: number with `state: "invalid"`
    - `concurrent_sessions`: current sum of `active_session_count` across active logins (i.e., bots in flight using your auth pool)
    - `concurrent_capacity`: `logins_active × per-login-capacity` (defaults to 20 per login)
    - `utilization_pct`: `concurrent_sessions / concurrent_capacity` as a percentage
    - `by_email_group`: per-pool breakdown of the same metrics

    **Use this with alert rules.** Configure a `meet_login_utilization` threshold alert at e.g. 70% to get notified before saturation, and configure `meet_login_unavailable` operational alerts to know when bots have actually hit the ceiling.

    **Polling:** Cheap to call. Numbers reflect live counters — no caching.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/meet-logins/utilization","method":"get"}]} />


---

## Meet Logins

Manage the Google Meet logins in a workspace login pool.

### Source: ./content/docs/api-v2/reference/meet-logins/index.mdx


Endpoints for the logins described in [Google Meet authenticated bots](/docs/api-v2/authenticated-bots/meet).


---

## List meet logins

### Source: ./content/docs/api-v2/reference/meet-logins/listMeetLogins.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

List all meet logins for your team across every workspace.

    Each row includes its parent `workspace_id` so you can correlate logins to their workspace without re-fetching. Sensitive fields (cert, key) live on the workspace, not the login, and are never returned through this endpoint.

    **State Field:** Logins can independently be `active` or `invalid`. A login flipping to `invalid` is rare in v1 — most SSO failures are workspace-scoped (cert mismatch) and disable the parent workspace instead. If a specific login is misbehaving (e.g., the Workspace user got suspended), you can manually delete and re-create it.

    **Filtering by workspace:** Not currently supported as a query param. Filter client-side using the `workspace_id` field on each row, or fetch the workspace and use `GET /v2/meet-workspaces/:id` for inventory.

    **Round-Robin Tracking:** `active_session_count` (current concurrent bots using this login) and `last_used_at` (last assignment timestamp) reflect the round-robin scheduler's view. Use these to sanity-check load distribution.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/meet-logins","method":"get"}]} />


---

## Update a meet login

### Source: ./content/docs/api-v2/reference/meet-logins/updateMeetLogin.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Update a meet login — rename it, change its email_group, or re-enable it after auto-disable.

    **Rename:** pass only `name`.

    **Update email_group:** pass `email_group` to change the round-robin pool the login belongs to. Pass an empty string to clear it (the login becomes pool-less and is only assignable via explicit `credential_id`). The new `email_group` value must satisfy the same domain rule as create — its domain must match (or be a subdomain of) the parent workspace's domain.

    **Re-enable after auto-disable:** pass `state: "active"`. Only valid if the login is currently `invalid`. Clears `failure_data`, `last_error_message`, and `last_error_at`.

    `email` and `workspace_id` are immutable. To change either, delete the login and create a new one.

    **State Restrictions:** PATCH cannot set `state` to `invalid` — that's system-only.

    **Error Scenarios:**
    - `400 Bad Request`: Attempted to set `state` to a value other than `active`.
    - `404 Not Found`: Login not found or does not belong to your team.
    - `422 Unprocessable Entity`: `email_group` domain does not match the parent workspace's domain.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/meet-logins/{credential_id}","method":"patch"}]} />


---

## Create a meet workspace

### Source: ./content/docs/api-v2/reference/meet-workspaces/createMeetWorkspace.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Create a meet workspace — the parent resource that holds your SAML cert + private key for one Google Workspace's SSO config.

    A meet workspace represents one Google Workspace whose Legacy SSO profile points at our `/v2/meet-sso/*` endpoints. The cert and key on the workspace are shared by every `meet_login` (Workspace user identity) you attach to it, mirroring how Google Workspace stores a single verification certificate per SSO profile.

    **Two creation paths, same response shape:**

    - **Server-generated keypair** — pass `generate_keypair: true`. The server creates a self-signed RSA-2048 keypair with 10-year validity. Use this if you don't already have a SAML cert and want the simplest setup.
    - **Bring-your-own keypair** — pass `cert_pem` and `private_key_pem` together. Use this if you want to manage your own crypto or already have a cert/key pair you trust.

    Mutually exclusive: provide either `generate_keypair: true` OR (`cert_pem` + `private_key_pem`), not both.

    **Response always includes `cert_pem`** so you can upload it to Google's Legacy SSO profile in your Workspace admin console. `private_key_pem` is never returned.

    **After creation, you must:**
    1. Upload the returned `cert_pem` to Google Admin Console → Security → Set up SSO with third-party IdP → Legacy SSO profile.
    2. Set Sign-in URL to `https://api.meetingbaas.com/v2/meet-sso/sign-in` and Sign-out URL to `https://api.meetingbaas.com/v2/meet-sso/sign-out` in the same SSO profile. Enable "Use a domain-specific issuer" and assign the SSO profile to all users.
    3. Create one or more Workspace users that bots will sign in as, complete the "Welcome to Workspace" interactive login for each, and set language to "English (United States)".
    4. Add `meet_logins` (one per Workspace user) referencing this `workspace_id`.
    5. Optionally call `POST /v2/meet-workspaces/:workspace_id/verify` to run pre-flight checks.

    **Security:** The cert and key are encrypted at rest using AES-256-GCM. `private_key_pem` is never echoed in any response — including subsequent GETs. If you need it back, you must rotate via PATCH.

    **Per-team uniqueness:** Each `domain` may exist at most once per team. Attempting to create a duplicate returns 409.

    **Error Scenarios:**
    - `409 Conflict`: A workspace for this `domain` already exists.
    - `422 Unprocessable Entity`: Invalid cert/key (parse failure or modulus mismatch); both `generate_keypair` and `cert_pem` provided; neither provided.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/meet-workspaces","method":"post"}]} />


---

## Delete a meet workspace (cascades to its logins)

### Source: ./content/docs/api-v2/reference/meet-workspaces/deleteMeetWorkspace.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Delete a meet workspace.

    **This cascades to every `meet_login` under the workspace.** All login rows are removed from the database in the same transaction; any bots currently assigned to those logins will have their `assigned_meet_login_id` set to `null` (the row deletion does not stop in-flight bots, but their post-bot accounting will reference a now-deleted login).

    **In-Use Guard:** If any login under this workspace has an active SSO session in flight (`active_session_count > 0`), the request returns 409 instead of deleting. Wait for the bots to finish or stop them first via the bots API. The aggregate count across all child logins is shown in the error message.

    **Operational Note:** This is a hard delete. Any reference to deleted login `credential_id` values from old bot records becomes a dangling reference. The cascade is irreversible — re-creating a workspace gives you a new `workspace_id` even if you reuse the same domain.

    Returns 404 if the workspace is not found or does not belong to your team.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/meet-workspaces/{workspace_id}","method":"delete"}]} />


---

## Get a meet workspace

### Source: ./content/docs/api-v2/reference/meet-workspaces/getMeetWorkspace.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Get full details for a single meet workspace.

    Returns the same fields as the list endpoint, scoped to one workspace. Useful for confirming the cert that was uploaded to Google matches the one we have on file (compare `cert_pem` against the Verification Certificate in your Google Admin Console).

    **Failure Context:** If `state` is `invalid`, the response includes `last_error_message`, `last_error_at`, and a structured `failure_data` object describing the bot run that triggered the auto-disable. Use this to investigate before re-enabling.

    Returns 404 if the workspace is not found or does not belong to your team.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/meet-workspaces/{workspace_id}","method":"get"}]} />


---

## Meet Workspaces

Manage Google Meet workspaces for authenticated bots.

### Source: ./content/docs/api-v2/reference/meet-workspaces/index.mdx


Endpoints for the workspaces described in [Google Meet authenticated bots](/docs/api-v2/authenticated-bots/meet).


---

## List meet workspaces

### Source: ./content/docs/api-v2/reference/meet-workspaces/listMeetWorkspaces.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

List all meet workspaces for your team.

    Returns each workspace's metadata including `workspace_id`, `name`, `domain`, `state`, the `cert_pem` (so you can re-upload to Google admin if needed), and any recent failure context. `private_key_pem` is never included.

    **State Field:**
    - `active`: workspace is healthy; bots assigned to logins under this workspace will sign in successfully.
    - `invalid`: the system auto-disabled the workspace because Google rejected our SAML assertion (typically a cert mismatch). Every login under an invalid workspace is blocked from being assigned. Re-upload the correct cert to Google admin and use PATCH to set `state` back to `active`.

    Use this endpoint to inventory your SAML SSO setup or build a dashboard listing workspaces by health.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/meet-workspaces","method":"get"}]} />


---

## Update a meet workspace

### Source: ./content/docs/api-v2/reference/meet-workspaces/updateMeetWorkspace.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Update a meet workspace — rename it, rotate its keypair, or re-enable it after auto-disable.

    **Rename:** pass only `name`.

    **Rotate cert/key:** pass both `cert_pem` and `private_key_pem` together. The new pair takes effect immediately for all logins under this workspace. You must upload the new cert to Google Admin Console at the same time — there is a brief window where the cert in our system and the cert in Google admin can diverge, during which bot sign-ins fail. Coordinate the swap.

    **Re-enable after auto-disable:** pass `state: "active"`. Only valid if the workspace is currently `invalid`. This clears `failure_data`, `last_error_message`, and `last_error_at` atomically. Only do this after fixing the underlying issue (e.g., re-uploading the correct cert to Google admin).

    `domain` is immutable. To change the domain, delete the workspace (after draining its logins) and create a new one.

    **State Restrictions:** PATCH cannot set `state` to `invalid` — that's system-only. Returns 400 if you try.

    **Error Scenarios:**
    - `400 Bad Request`: Attempted to set `state` to a value other than `active`.
    - `404 Not Found`: Workspace not found or does not belong to your team.
    - `422 Unprocessable Entity`: Cert/key parse failure or mismatched pair; `cert_pem` provided without `private_key_pem` (or vice versa).

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/meet-workspaces/{workspace_id}","method":"patch"}]} />


---

## Go back to MeetingBaas storage

### Source: ./content/docs/api-v2/reference/storage/deleteStorageConfig.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Go back to MeetingBaas-managed storage.

    From the next bot onwards, artifacts are stored by MeetingBaas again.

    **Nothing is deleted.** Artifacts already in your buckets stay where they are and stay accessible through the API and signed URLs — bots recorded while the configuration was active keep resolving to it. Your credentials are retained for exactly that purpose. Removing our access to those buckets on your side will make those older artifacts unreadable to us.

    Returns 404 if you have no configuration.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/storage-config","method":"delete"}]} />


---

## Get your storage configuration

### Source: ./content/docs/api-v2/reference/storage/getStorageConfig.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Get the object-storage configuration your artifacts are currently written to.

    Returns 404 when you have not configured one — that is the default, and means MeetingBaas stores your artifacts.

    The `secret_access_key` is never returned. `access_key_id` is, so you can tell which credential is in use.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/storage-config","method":"get"}]} />


---

## Storage

Configure, verify, and remove your own S3-compatible storage.

### Source: ./content/docs/api-v2/reference/storage/index.mdx


Endpoints for the storage configuration described in [Bring Your Own Storage](/docs/bring-your-own-storage).


---

## Set your storage configuration

### Source: ./content/docs/api-v2/reference/storage/setStorageConfig.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Point MeetingBaas at object storage you own.

    From the next bot onwards, every artifact we produce for you — recording, audio chunks, raw and diarized transcripts, screenshots and bot logs — is written to your buckets with your credentials. Nothing lands in MeetingBaas storage.

    **Verified before it is accepted.** The ingest key writes a small marker object into each bucket and the service key reads, writes and deletes it — each operation exercised with the credential that will really perform it, which also catches two key pairs that point at different buckets. Ingest upload receives recordings; service upload writes transcripts and reconciled artifacts; read serves artifacts; and delete enforces data retention. If any step fails the request returns 400 with the reason and your existing configuration is left untouched.

    **Settings:** `endpoint`, `region` and `force_path_style` are the same knobs as a self-hosted deployment. Any S3-compatible provider works; enable `force_path_style` for MinIO, Ceph and most self-hosted gateways. The three buckets may all be the same bucket — keys are prefixed with the bot id regardless.

    **Two credentials.** The **ingest** key is write-only (`s3:PutObject`, `s3:PutObjectTagging`, `s3:AbortMultipartUpload`) and is the only one that leaves our infrastructure — it is handed to the recording bot, which runs a browser inside your meeting. Scoped this way, a compromised bot can add objects and nothing else. The **service** key (`s3:GetObject`, `s3:ListBucket`, `s3:PutObject`, `s3:DeleteObject`) stays in our API and does everything else: serving your recordings back, writing transcripts, and deleting artifacts when your retention period expires.

    **Keep the buckets private** — artifacts are served through short-lived signed URLs — but they must be reachable from the public internet, because transcription providers fetch audio directly from a signed URL.

    **Data residency.** By default (`allow_transient_spill: false`) nothing you record ever rests on MeetingBaas storage: if an upload to your bucket fails and retries are exhausted, the artifact is reported as failed and lost rather than parked on our infrastructure. Set it to `true` if you would rather we hold a copy until the upload can be retried.

    **Replacing a configuration is safe.** Calling this again supersedes the previous configuration for new bots only. Bots recorded earlier keep resolving to the storage they were written to, so their artifacts stay readable and deletable — nothing is migrated or re-pointed.

    Returns 200 with the stored configuration.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/storage-config","method":"put"}]} />


---

## Check that MeetingBaas can still use your buckets

### Source: ./content/docs/api-v2/reference/storage/testStorageConfig.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Re-run the access check against your current configuration and record the result.

    Writes, reads back and deletes a marker object in each bucket, exactly as the check that runs when a configuration is set. Use it after rotating a key or changing a bucket policy: credentials that quietly stopped working would otherwise first surface as a failed upload at the end of a real meeting.

    Returns 200 with `ok: true` when the storage is healthy, or 200 with `ok: false` and the reason when it is not — the request succeeded either way, it is the storage that is unhealthy. Returns 404 if you have no configuration.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/storage-config/test","method":"post"}]} />


---

## Create a teams login

### Source: ./content/docs/api-v2/reference/teams-logins/createTeamsLogin.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/teams-logins","method":"post"}]} />


---

## Delete a teams login

### Source: ./content/docs/api-v2/reference/teams-logins/deleteTeamsLogin.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/teams-logins/{credential_id}","method":"delete"}]} />


---

## Get a teams login

### Source: ./content/docs/api-v2/reference/teams-logins/getTeamsLogin.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/teams-logins/{credential_id}","method":"get"}]} />


---

## Get current login pool utilization

### Source: ./content/docs/api-v2/reference/teams-logins/getTeamsLoginUtilization.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/teams-logins/utilization","method":"get"}]} />


---

## Teams Logins

Manage the Microsoft Teams logins in a workspace login pool.

### Source: ./content/docs/api-v2/reference/teams-logins/index.mdx


Endpoints for the logins described in [Microsoft Teams authenticated bots](/docs/api-v2/authenticated-bots/teams).


---

## List teams logins

### Source: ./content/docs/api-v2/reference/teams-logins/listTeamsLogins.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/teams-logins","method":"get"}]} />


---

## Update a teams login

### Source: ./content/docs/api-v2/reference/teams-logins/updateTeamsLogin.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/teams-logins/{credential_id}","method":"patch"}]} />


---

## Create a teams workspace

### Source: ./content/docs/api-v2/reference/teams-workspaces/createTeamsWorkspace.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/teams-workspaces","method":"post"}]} />


---

## Delete a teams workspace (cascades to its logins)

### Source: ./content/docs/api-v2/reference/teams-workspaces/deleteTeamsWorkspace.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/teams-workspaces/{workspace_id}","method":"delete"}]} />


---

## Get a teams workspace

### Source: ./content/docs/api-v2/reference/teams-workspaces/getTeamsWorkspace.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/teams-workspaces/{workspace_id}","method":"get"}]} />


---

## Teams Workspaces

Manage Microsoft Teams workspaces for authenticated bots.

### Source: ./content/docs/api-v2/reference/teams-workspaces/index.mdx


Endpoints for the workspaces described in [Microsoft Teams authenticated bots](/docs/api-v2/authenticated-bots/teams).


---

## List teams workspaces

### Source: ./content/docs/api-v2/reference/teams-workspaces/listTeamsWorkspaces.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/teams-workspaces","method":"get"}]} />


---

## Update a teams workspace

### Source: ./content/docs/api-v2/reference/teams-workspaces/updateTeamsWorkspace.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/teams-workspaces/{workspace_id}","method":"patch"}]} />


---

## Bot Chat Message

Bot Chat Message payload structure

### Source: ./content/docs/api-v2/reference/webhooks/botwebhookchatmessage.mdx




## Payload Structure

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `data` | object | Yes |  |
| `event` | string | Yes | The webhook event type |
| `extra` | object | null | Yes | Additional metadata provided when creating the bot. This is user-defined data that can be used for correlation or tracking |

## Field Details

- **`data`** (object) **Required**

  Properties:
    - **`bot_id`** (string (uuid)) **Required**
      The UUID of the bot that received the chat message

    - **`event_id`** (string (uuid) | null) **Required**
      The UUID of the calendar event associated with this bot. Null for non-calendar bots

    - **`message_id`** (string) **Required**
      Unique identifier of the chat message

    - **`sender_id`** (integer | null) **Required**
      Sequential participant ID of the sender. Null if the sender could not be resolved to a participant

    - **`sender_name`** (string) **Required**
      Display name of the message sender

    - **`sent_at`** (string (date-time)) **Required**
      ISO 8601 timestamp when this webhook was sent

    - **`text`** (string) **Required**
      Text content of the chat message


- **`event`** (string) **Required**
  The webhook event type

- **`extra`** (object | null) **Required**
  Additional metadata provided when creating the bot. This is user-defined data that can be used for correlation or tracking


## Example

```json
{
  "data": {
    "bot_id": "examplebot_id",
    "event_id": null,
    "message_id": "examplemessage_id",
    "sender_id": null,
    "sender_name": "examplesender_name",
    "sent_at": "examplesent_at",
    "text": "exampletext"
  },
  "event": "exampleevent",
  "extra": null
}
```


---

## Bot Chat Status

Bot Chat Status payload structure

### Source: ./content/docs/api-v2/reference/webhooks/botwebhookchatstatus.mdx




## Payload Structure

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `data` | object | Yes |  |
| `event` | string | Yes | The webhook event type |
| `extra` | object | null | Yes | Additional metadata provided when creating the bot. This is user-defined data that can be used for correlation or tracking |

## Field Details

- **`data`** (object) **Required**

  Properties:
    - **`available`** (boolean) **Required**
      Whether the bot can send and receive chat in this meeting

    - **`bot_id`** (string) **Required**
      The UUID of the bot this chat status refers to

    - **`event_id`** (string (uuid) | null) **Required**
      The UUID of the calendar event series. Null when the bot was not created from a calendar event

    - **`reason`** ("organizer_disabled" | "panel_not_attached" | "send_failed" | null) **Required**
      Why chat is unavailable. Null when available is true

    - **`sent_at`** (string (date-time)) **Required**
      ISO 8601 timestamp when this webhook was sent


- **`event`** (string) **Required**
  The webhook event type

- **`extra`** (object | null) **Required**
  Additional metadata provided when creating the bot. This is user-defined data that can be used for correlation or tracking


## Example

```json
{
  "data": {
    "available": true,
    "bot_id": "examplebot_id",
    "event_id": null,
    "reason": null,
    "sent_at": "examplesent_at"
  },
  "event": "exampleevent",
  "extra": null
}
```


---

## Bot Completed

Bot Completed payload structure

### Source: ./content/docs/api-v2/reference/webhooks/botwebhookcompleted.mdx




## Payload Structure

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `data` | object | Yes |  |
| `event` | string | Yes | The webhook event type |
| `extra` | object | null | Yes | Additional metadata provided when creating the bot. This is user-defined data that can be used for correlation or tracking |

## Field Details

- **`data`** (object) **Required**

  Properties:
    - **`audio`** (string (uri) | null) **Required**
      Signed URL to download the audio recording. Valid for 4 hours. Null if audio recording is not available or has been deleted

    - **`bot_id`** (string (uuid)) **Required**
      The UUID of the bot that completed

    - **`data_deleted`** (boolean) **Required**
      Whether the bot's data (artifacts, recordings) has been deleted. True if data has been permanently removed

    - **`diarization`** (string (uri) | null) **Required**
      Signed URL to download the speaker diarization data. Valid for 4 hours. Null if diarization is not available or has been deleted

    - **`duration_seconds`** (integer | null) **Required**

    - **`event_id`** (string (uuid) | null) **Required**
      The UUID of the calendar event associated with this bot. Null for non-calendar bots

    - **`exited_at`** (string (date-time) | null) **Required**
      ISO 8601 timestamp when the bot exited the meeting. Null if exit time is not available

    - **`joined_at`** (string (date-time) | null) **Required**
      ISO 8601 timestamp when the bot joined the meeting. Null if join time is not available

    - **`participants`** (object[]) **Required**
      List of participants who joined the meeting with their names and metadata. Empty array if participant information is not available

    - **`raw_transcription`** (string (uri) | null) **Required**
      Signed URL to download the raw transcription file. Valid for 4 hours. Null if raw transcription is not available or has been deleted

    - **`sent_at`** (string (date-time)) **Required**
      ISO 8601 timestamp when this webhook was sent

    - **`speakers`** (object[]) **Required**
      List of speakers detected in the meeting with their names and metadata. Empty array if speaker information is not available

    - **`transcription`** (string (uri) | null) **Required**
      Signed URL to download the processed transcription file. Valid for 4 hours. Null if transcription is not available or has been deleted

    - **`transcription_ids`** (string[] | null) **Required**
      Array of transcription job IDs from the transcription provider. Null if transcription was not enabled or if IDs are not available

    - **`transcription_provider`** (string | null) **Required**
      The transcription provider used (e.g., 'gladia', 'deepgram', 'assemblyai'). Null if transcription was not enabled or if provider information is not available

    - **`video`** (string (uri) | null) **Required**
      Signed URL to download the video recording. Valid for 4 hours. Null if video recording is not available or has been deleted


- **`event`** (string) **Required**
  The webhook event type

- **`extra`** (object | null) **Required**
  Additional metadata provided when creating the bot. This is user-defined data that can be used for correlation or tracking


## Example

```json
{
  "data": {
    "audio": null,
    "bot_id": "examplebot_id",
    "data_deleted": true,
    "diarization": null,
    "duration_seconds": null,
    "event_id": null,
    "exited_at": null,
    "joined_at": null,
    "participants": [],
    "raw_transcription": null,
    "sent_at": "examplesent_at",
    "speakers": [],
    "transcription": null,
    "transcription_ids": [],
    "transcription_provider": null,
    "video": null
  },
  "event": "exampleevent",
  "extra": null
}
```


---

## Bot Failed

Bot Failed payload structure

### Source: ./content/docs/api-v2/reference/webhooks/botwebhookfailed.mdx




## Payload Structure

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `data` | object | Yes |  |
| `event` | string | Yes | The webhook event type |
| `extra` | object | null | Yes | Additional metadata provided when creating the bot. This is user-defined data that can be used for correlation or tracking |

## Field Details

- **`data`** (object) **Required**

  Properties:
    - **`bot_id`** (string (uuid)) **Required**
      The UUID of the bot that failed

    - **`error_code`** (string) **Required**
      Machine-readable error code for programmatic handling. Common codes include 'MEETING_NOT_FOUND', 'MEETING_ENDED', 'BOT_CRASHED', etc.

    - **`error_message`** (string) **Required**
      Human-readable error message describing why the bot failed

    - **`event_id`** (string (uuid) | null) **Required**
      The UUID of the calendar event associated with this bot. Null for non-calendar bots

    - **`sent_at`** (string (date-time)) **Required**
      ISO 8601 timestamp when this webhook was sent


- **`event`** (string) **Required**
  The webhook event type

- **`extra`** (object | null) **Required**
  Additional metadata provided when creating the bot. This is user-defined data that can be used for correlation or tracking


## Example

```json
{
  "data": {
    "bot_id": "examplebot_id",
    "error_code": "exampleerror_code",
    "error_message": "exampleerror_message",
    "event_id": null,
    "sent_at": "examplesent_at"
  },
  "event": "exampleevent",
  "extra": null
}
```


---

## Bot Status Change

Bot Status Change payload structure

### Source: ./content/docs/api-v2/reference/webhooks/botwebhookstatuschange.mdx




## Payload Structure

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `data` | object | Yes |  |
| `event` | string | Yes | The webhook event type |
| `extra` | object | null | Yes | Additional metadata provided when creating the bot. This is user-defined data that can be used for correlation or tracking |

## Field Details

- **`data`** (object) **Required**

  Properties:
    - **`bot_id`** (string (uuid)) **Required**
      The UUID of the bot that changed status

    - **`event_id`** (string (uuid) | null) **Required**
      The UUID of the calendar event associated with this bot. Null for non-calendar bots

    - **`status`** (object) **Required**
      Status information with code, timestamp, and optional status-specific fields


- **`event`** (string) **Required**
  The webhook event type

- **`extra`** (object | null) **Required**
  Additional metadata provided when creating the bot. This is user-defined data that can be used for correlation or tracking


## Example

```json
{
  "data": {
    "bot_id": "examplebot_id",
    "event_id": null,
    "status": {
      "attempt": 0,
      "code": "examplecode",
      "created_at": "examplecreated_at",
      "error_message": "exampleerror_message",
      "max": 0,
      "start_time": 0
    }
  },
  "event": "exampleevent",
  "extra": null
}
```


---

## Calendar Connection Created

Calendar Connection Created payload structure

### Source: ./content/docs/api-v2/reference/webhooks/calendarwebhookconnectioncreated.mdx




## Payload Structure

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `data` | object | Yes |  |
| `event` | string | Yes | The webhook event type |

## Field Details

- **`data`** (object) **Required**

  Properties:
    - **`account_email`** (string) **Required**
      The email address associated with the calendar account

    - **`calendar_id`** (string (uuid)) **Required**
      The UUID of the newly created calendar connection

    - **`calendar_platform`** ("google" | "microsoft") **Required**
      The calendar platform. Either 'google' for Google Calendar or 'microsoft' for Microsoft Outlook/365

    - **`created_at`** (string (date-time)) **Required**
      ISO 8601 timestamp when the calendar connection was created

    - **`status`** ("active" | "error" | "revoked" | "permission_denied") **Required**
      The current status of the calendar connection. Possible values: 'active' (connection is working), 'error' (connection has errors), 'revoked' (OAuth access was revoked), 'permission_denied' (insufficient permissions)


- **`event`** (string) **Required**
  The webhook event type


## Example

```json
{
  "data": {
    "account_email": "exampleaccount_email",
    "calendar_id": "examplecalendar_id",
    "calendar_platform": "examplecalendar_platform",
    "created_at": "examplecreated_at",
    "status": "examplestatus"
  },
  "event": "exampleevent"
}
```


---

## Calendar Connection Deleted

Calendar Connection Deleted payload structure

### Source: ./content/docs/api-v2/reference/webhooks/calendarwebhookconnectiondeleted.mdx




## Payload Structure

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `data` | object | Yes |  |
| `event` | string | Yes | The webhook event type |

## Field Details

- **`data`** (object) **Required**

  Properties:
    - **`calendar_id`** (string (uuid)) **Required**
      The UUID of the deleted calendar connection

    - **`calendar_platform`** ("google" | "microsoft") **Required**
      The calendar platform. Either 'google' for Google Calendar or 'microsoft' for Microsoft Outlook/365

    - **`deleted_at`** (string (date-time)) **Required**
      ISO 8601 timestamp when the calendar connection was deleted


- **`event`** (string) **Required**
  The webhook event type


## Example

```json
{
  "data": {
    "calendar_id": "examplecalendar_id",
    "calendar_platform": "examplecalendar_platform",
    "deleted_at": "exampledeleted_at"
  },
  "event": "exampleevent"
}
```


---

## Calendar Connection Updated

Calendar Connection Updated payload structure

### Source: ./content/docs/api-v2/reference/webhooks/calendarwebhookconnectionupdated.mdx




## Payload Structure

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `data` | object | Yes |  |
| `event` | string | Yes | The webhook event type |

## Field Details

- **`data`** (object) **Required**

  Properties:
    - **`account_email`** (string) **Required**
      The email address associated with the calendar account

    - **`calendar_id`** (string (uuid)) **Required**
      The UUID of the updated calendar connection

    - **`calendar_platform`** ("google" | "microsoft") **Required**
      The calendar platform. Either 'google' for Google Calendar or 'microsoft' for Microsoft Outlook/365

    - **`created_at`** (string (date-time)) **Required**
      ISO 8601 timestamp when the calendar connection was originally created

    - **`status`** ("active" | "error" | "revoked" | "permission_denied") **Required**
      The current status of the calendar connection after the update. Possible values: 'active' (connection is working), 'error' (connection has errors), 'revoked' (OAuth access was revoked), 'permission_denied' (insufficient permissions)

    - **`updated_at`** (string (date-time)) **Required**
      ISO 8601 timestamp when the calendar connection was updated


- **`event`** (string) **Required**
  The webhook event type


## Example

```json
{
  "data": {
    "account_email": "exampleaccount_email",
    "calendar_id": "examplecalendar_id",
    "calendar_platform": "examplecalendar_platform",
    "created_at": "examplecreated_at",
    "status": "examplestatus",
    "updated_at": "exampleupdated_at"
  },
  "event": "exampleevent"
}
```


---

## Calendar Event Cancelled

Calendar Event Cancelled payload structure

### Source: ./content/docs/api-v2/reference/webhooks/calendarwebhookeventcancelled.mdx




## Payload Structure

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `data` | object | Yes |  |
| `event` | string | Yes | The webhook event type |

## Field Details

- **`data`** (object) **Required**

  Properties:
    - **`calendar_id`** (string (uuid)) **Required**
      The UUID of the calendar connection where the event was cancelled

    - **`cancelled_instances`** (object[]) **Required**
      Array of event instances that were cancelled. For one-off events, this contains a single instance. For recurring events, this contains all instances that were cancelled

    - **`event_type`** ("one_off" | "recurring") **Required**
      The type of event. 'one_off' for single events, 'recurring' for events that are part of a recurring series

    - **`series_id`** (string (uuid) | null) **Required**
      The UUID of the event series. Null only in rare cases where the series relationship could not be established


- **`event`** (string) **Required**
  The webhook event type


## Example

```json
{
  "data": {
    "calendar_id": "examplecalendar_id",
    "cancelled_instances": [],
    "event_type": "exampleevent_type",
    "series_id": null
  },
  "event": "exampleevent"
}
```


---

## Calendar Event Created

Calendar Event Created payload structure

### Source: ./content/docs/api-v2/reference/webhooks/calendarwebhookeventcreated.mdx




## Payload Structure

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `data` | object | Yes |  |
| `event` | string | Yes | The webhook event type |

## Field Details

- **`data`** (object) **Required**

  Properties:
    - **`calendar_id`** (string (uuid)) **Required**
      The UUID of the calendar connection where the event was created

    - **`event_type`** ("one_off" | "recurring") **Required**
      The type of event. 'one_off' for single events, 'recurring' for events that are part of a recurring series

    - **`instances`** (object[]) **Required**
      Array of event instances that were created. For one-off events, this contains a single instance. For recurring events, this contains all instances that were created

    - **`series_bot_scheduled`** (boolean) **Required**
      Whether a bot has been scheduled for all occurrences of this series. True if a calendar bot schedule exists for the entire series

    - **`series_id`** (string (uuid) | null) **Required**
      The UUID of the event series. Null only in rare cases where the series relationship could not be established


- **`event`** (string) **Required**
  The webhook event type


## Example

```json
{
  "data": {
    "calendar_id": "examplecalendar_id",
    "event_type": "exampleevent_type",
    "instances": [],
    "series_bot_scheduled": true,
    "series_id": null
  },
  "event": "exampleevent"
}
```


---

## Calendar Events Synced

Calendar Events Synced payload structure

### Source: ./content/docs/api-v2/reference/webhooks/calendarwebhookeventssynced.mdx




## Payload Structure

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `data` | object | Yes |  |
| `event` | string | Yes | The webhook event type |

## Field Details

- **`data`** (object) **Required**

  Properties:
    - **`calendar_id`** (string (uuid)) **Required**
      The UUID of the calendar connection that was synced

    - **`events`** (object[]) **Required**
      Array of event series that were synced. Each series contains its event instances


- **`event`** (string) **Required**
  The webhook event type


## Example

```json
{
  "data": {
    "calendar_id": "examplecalendar_id",
    "events": []
  },
  "event": "exampleevent"
}
```


---

## Calendar Event Updated

Calendar Event Updated payload structure

### Source: ./content/docs/api-v2/reference/webhooks/calendarwebhookeventupdated.mdx




## Payload Structure

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `data` | object | Yes |  |
| `event` | string | Yes | The webhook event type |

## Field Details

- **`data`** (object) **Required**

  Properties:
    - **`affected_instances`** (object[]) **Required**
      Array of event instances that were affected by the update. This includes the instance that was directly updated and any related instances

    - **`calendar_id`** (string (uuid)) **Required**
      The UUID of the calendar connection where the event was updated

    - **`event_type`** ("one_off" | "recurring") **Required**
      The type of event. 'one_off' for single events, 'recurring' for events that are part of a recurring series

    - **`is_exception`** (boolean) **Required**
      Whether the updated instance is an exception to a recurring series. True if this instance has been modified differently from the recurring pattern

    - **`series_bot_scheduled`** (boolean) **Required**
      Whether a bot has been scheduled for all occurrences of this series. True if a calendar bot schedule exists for the entire series

    - **`series_id`** (string (uuid) | null) **Required**
      The UUID of the event series. Null only in rare cases where the series relationship could not be established


- **`event`** (string) **Required**
  The webhook event type


## Example

```json
{
  "data": {
    "affected_instances": [],
    "calendar_id": "examplecalendar_id",
    "event_type": "exampleevent_type",
    "is_exception": true,
    "series_bot_scheduled": true,
    "series_id": null
  },
  "event": "exampleevent"
}
```


---

## Webhook Payloads

Reference documentation for all webhook payload structures

### Source: ./content/docs/api-v2/reference/webhooks/index.mdx


This section contains reference documentation for all webhook payload structures sent by Meeting BaaS v2.

## Bot Webhooks

- [Bot Webhook Chat Message](/docs/api-v2/reference/webhooks/botwebhookchatmessage)
- [Bot Webhook Chat Status](/docs/api-v2/reference/webhooks/botwebhookchatstatus)
- [Bot Webhook Completed](/docs/api-v2/reference/webhooks/botwebhookcompleted)
- [Bot Webhook Failed](/docs/api-v2/reference/webhooks/botwebhookfailed)
- [Bot Webhook Status Change](/docs/api-v2/reference/webhooks/botwebhookstatuschange)

## Calendar Webhooks

- [Calendar Webhook Connection Created](/docs/api-v2/reference/webhooks/calendarwebhookconnectioncreated)
- [Calendar Webhook Connection Deleted](/docs/api-v2/reference/webhooks/calendarwebhookconnectiondeleted)
- [Calendar Webhook Connection Updated](/docs/api-v2/reference/webhooks/calendarwebhookconnectionupdated)
- [Calendar Webhook Event Cancelled](/docs/api-v2/reference/webhooks/calendarwebhookeventcancelled)
- [Calendar Webhook Event Created](/docs/api-v2/reference/webhooks/calendarwebhookeventcreated)
- [Calendar Webhook Event Updated](/docs/api-v2/reference/webhooks/calendarwebhookeventupdated)
- [Calendar Webhook Events Synced](/docs/api-v2/reference/webhooks/calendarwebhookeventssynced)


---

## Create a Zoom credential

### Source: ./content/docs/api-v2/reference/zoom-credentials/createZoomCredential.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Create a new Zoom credential for your team.

    Zoom credentials store your Zoom OAuth App credentials (client_id and client_secret) securely encrypted. You can create two types of credentials:

    **App-only credentials:** Provide only `name`, `client_id`, and `client_secret`. These credentials can be used for SDK authentication when bots join meetings.

    **User-authorized credentials:** Additionally provide `authorization_code` and `redirect_uri`. The API will exchange the authorization code for OAuth tokens, enabling OBF (On-Behalf-Of) token support. OBF tokens allow bots to join meetings on behalf of a specific Zoom user. The authorising user's `zoom_email` and `zoom_display_name` are captured from Zoom's `/users/me` API at exchange time (requires the `user:read:user` scope) and returned in the response so you can show end users which Zoom account is connected.

    **Custom Metadata:** Pass an optional `extra` JSON object to tag the credential with your own key-value pairs (for example an internal user ID, environment, or tenant). The data is stored as-is, returned by all credential endpoints, and is filterable on the list endpoint via the `extra` query parameter.

    **Security:** All credentials are encrypted at rest using AES-256-GCM. Client secrets and OAuth tokens are never returned in API responses.

    **OAuth Flow:** To obtain an authorization code, redirect users to Zoom's OAuth authorization endpoint and capture the code from the callback. Ensure your redirect URI exactly matches the one registered in your Zoom OAuth App.

    **Error Scenarios:**
    - `400 Bad Request`: Invalid input or missing redirect_uri when authorization_code is provided
    - `400 Bad Request`: Failed to exchange authorization code (invalid code or redirect_uri mismatch)

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/zoom-credentials","method":"post"}]} />


---

## Delete a Zoom credential

### Source: ./content/docs/api-v2/reference/zoom-credentials/deleteZoomCredential.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Delete a Zoom credential (soft delete).

    The credential is marked as deleted and will no longer appear in list responses. Bots currently using this credential will fail to fetch OBF tokens.

    **Impact on Bots:** If bots are configured to use this credential (via `zoom_config.credential_id`), they will fail with an error when trying to fetch OBF tokens. Make sure to update any bot configurations before deleting a credential.

    **Soft Delete:** The credential is soft-deleted and can potentially be restored by support if needed. All associated encrypted data remains in the database.

    Returns 404 if the credential is not found or does not belong to your team.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/zoom-credentials/{id}","method":"delete"}]} />


---

## Get a Zoom credential

### Source: ./content/docs/api-v2/reference/zoom-credentials/getZoomCredential.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Get detailed information about a specific Zoom credential.

    Returns the credential's metadata including its current state and any recent errors. Sensitive fields (client_secret, OAuth tokens) are never included.

    For "user" type credentials, the response also includes `zoom_email` and `zoom_display_name` (captured from Zoom's `/users/me` API at OAuth time) and any `extra` JSON metadata you attached at creation or via `PATCH`.

    **Error Tracking:** The `last_error_message` and `last_error_at` fields show the most recent OBF token fetch failure. Check these fields if bots using this credential are failing to join meetings.

    Returns 404 if the credential is not found or does not belong to your team.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/zoom-credentials/{id}","method":"get"}]} />


---

## Zoom Credentials

Manage the Zoom credentials used to send authenticated bots.

### Source: ./content/docs/api-v2/reference/zoom-credentials/index.mdx


Endpoints for the credentials described in [Zoom credentials](/docs/api-v2/authenticated-bots/zoom/credentials).


---

## List Zoom credentials

### Source: ./content/docs/api-v2/reference/zoom-credentials/listZoomCredentials.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

List all Zoom credentials for your team.

    Returns all non-deleted credentials with their metadata. Sensitive fields (client_secret, OAuth tokens) are never included in responses.

    **Response Fields:**
    - `credential_id`: UUID to reference this credential in bot requests
    - `name`: User-friendly name for identification
    - `credential_type`: "app" (SDK only) or "user" (with OAuth tokens)
    - `zoom_user_id`: The Zoom user ID (only for "user" type)
    - `zoom_email` / `zoom_display_name`: The authorising Zoom user's email and display name, captured from Zoom's `/users/me` API at OAuth time (only for "user" type, requires the `user:read:user` scope)
    - `state`: "active" or "invalid"
    - `last_error_message`: Last OBF token fetch error (if any)
    - `extra`: The optional user-supplied JSON metadata attached to the credential

    **Filtering:** Narrow the result set with optional query parameters (combined with AND):
    - `name`, `zoom_email`, `zoom_display_name`: case-insensitive partial match
    - `zoom_user_id`: exact match
    - `credential_type`, `state`: comma-separated enum lists (e.g. `credential_type=user`, `state=active,invalid`)
    - `extra`: `key:value` pairs against the `extra` JSON payload, comma-separated for multiple conditions (e.g. `extra=internal_user_id:u_42,environment:production`). Values are matched exactly (case-sensitive); credentials missing the key are excluded.

    **Error Tracking:** If bots fail to fetch OBF tokens using a credential, the error is recorded in `last_error_message` and `last_error_at`. These fields are cleared on successful OBF token fetch.

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/zoom-credentials","method":"get"}]} />


---

## Update a Zoom credential

### Source: ./content/docs/api-v2/reference/zoom-credentials/updateZoomCredential.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Update an existing Zoom credential.

    You can update the credential name, SDK credentials (client_id/client_secret), the user-supplied `extra` metadata, or re-authorize with new OAuth tokens.

    **Updating Name:** Provide only `name` to rename the credential.

    **Updating SDK Credentials:** Provide both `client_id` and `client_secret` together to update the SDK credentials.

    **Updating Custom Metadata:** Provide `extra` to replace the credential's metadata payload, or send `"extra": null` to clear it.

    **Re-authorizing:** Provide `authorization_code`, `redirect_uri`, `client_id`, and `client_secret` to exchange a new authorization code for fresh OAuth tokens. This also refreshes `zoom_email` and `zoom_display_name` from Zoom's `/users/me` API, resets the credential state to "active", and clears any error messages.

    **Error Scenarios:**
    - `400 Bad Request`: Missing redirect_uri when authorization_code is provided
    - `400 Bad Request`: Failed to exchange authorization code
    - `404 Not Found`: Credential not found or does not belong to your team

<APIPage document={"./openapi-v2.json"} operations={[{"path":"/v2/zoom-credentials/{id}","method":"patch"}]} />


---

## Create Calendar

### Source: ./content/docs/api/reference/calendars/create_calendar.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Integrates a new calendar with the system using OAuth credentials. This endpoint establishes a connection with the calendar provider (Google, Microsoft), sets up webhook notifications for real-time updates, and performs an initial sync of all calendar events. It requires OAuth credentials (client ID, client secret, and refresh token) and the platform type. Once created, the calendar is assigned a unique UUID that should be used for all subsequent operations. Returns the newly created calendar object with all integration details.

<APIPage document={"./openapi.json"} operations={[{"path":"/calendars/","method":"post"}]} />


---

## Delete Calendar

### Source: ./content/docs/api/reference/calendars/delete_calendar.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Permanently removes a calendar integration by its UUID, including all associated events and bot configurations. This operation cancels any active subscriptions with the calendar provider, stops all webhook notifications, and unschedules any pending recordings. All related resources are cleaned up in the database. This action cannot be undone, and subsequent requests to this calendar's UUID will return 404 Not Found errors.

<APIPage document={"./openapi.json"} operations={[{"path":"/calendars/{uuid}","method":"delete"}]} />

---

## Get Calendar

### Source: ./content/docs/api/reference/calendars/get_calendar.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieves detailed information about a specific calendar integration by its UUID. Returns comprehensive calendar data including the calendar name, email address, provider details (Google, Microsoft), sync status, and other metadata. This endpoint is useful for displaying calendar information to users or verifying the status of a calendar integration before performing operations on its events.

<APIPage document={"./openapi.json"} operations={[{"path":"/calendars/{uuid}","method":"get"}]} />


---

## Get Event

### Source: ./content/docs/api/reference/calendars/get_event.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieves comprehensive details about a specific calendar event by its UUID. Returns complete event information including title, meeting link, start and end times, organizer status, recurrence information, and the full list of attendees with their names and email addresses. Also includes any associated bot parameters if recording is scheduled for this event. The raw calendar data from the provider is also included for advanced use cases.

<APIPage document={"./openapi.json"} operations={[{"path":"/calendar_events/{uuid}","method":"get"}]} />


---

## List Calendars

### Source: ./content/docs/api/reference/calendars/list_calendars.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieves all calendars that have been integrated with the system for the authenticated user. Returns a list of calendars with their names, email addresses, provider information, and sync status. This endpoint shows only calendars that have been formally connected through the create_calendar endpoint, not all available calendars from the provider.

<APIPage document={"./openapi.json"} operations={[{"path":"/calendars/","method":"get"}]} />

---

## List Events

### Source: ./content/docs/api/reference/calendars/list_events.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieves a paginated list of calendar events with comprehensive filtering options. Supports filtering by organizer email, attendee email, date ranges (start_date_gte, start_date_lte), and event status. Results can be limited to upcoming events (default), past events, or all events. Each event includes full details such as meeting links, participants, and recording status. The response includes a 'next' pagination cursor for retrieving additional results.

<APIPage document={"./openapi.json"} operations={[{"path":"/calendar_events/","method":"get"}]} />


---

## List Raw Calendars

### Source: ./content/docs/api/reference/calendars/list_raw_calendars.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieves unprocessed calendar data directly from the provider (Google, Microsoft) using provided OAuth credentials. This endpoint is typically used during the initial setup process to allow users to select which calendars to integrate. Returns a list of available calendars with their unique IDs, email addresses, and primary status. This data is not persisted until a calendar is formally created using the create_calendar endpoint.

<APIPage document={"./openapi.json"} operations={[{"path":"/calendars/raw","method":"post"}]} />


---

## Patch Bot

### Source: ./content/docs/api/reference/calendars/patch_bot.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Updates the configuration of a bot already scheduled to record an event. Allows modification of recording settings, webhook URLs, and other bot parameters without canceling and recreating the scheduled recording. For recurring events, the 'all_occurrences' parameter determines whether changes apply to all instances or just the specific occurrence. Returns the updated event(s) with the modified bot parameters.

<APIPage document={"./openapi.json"} operations={[{"path":"/calendar_events/{uuid}/bot","method":"patch"}]} />


---

## Resync All Calendars

### Source: ./content/docs/api/reference/calendars/resync_all_calendars.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Forces a sync of all your connected calendars with their providers (Google, Microsoft).

Processes each calendar individually and returns:
- `synced_calendars`: UUIDs of successfully synced calendars
- `errors`: Details of any failures

Sends webhook notifications for calendars with updates.

<APIPage document={"./openapi.json"} operations={[{"path":"/calendars/resync_all","method":"post"}]} />


---

## Schedule Record Event

### Source: ./content/docs/api/reference/calendars/schedule_record_event.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Configures a bot to automatically join and record a specific calendar event at its scheduled time. The UUID in the request path is the event UUID. The request body contains detailed bot configuration, including recording options, streaming settings, and webhook notification URLs. For recurring events, the 'all_occurrences' parameter can be set to true to schedule recording for all instances of the recurring series, or false (default) to schedule only the specific instance. Returns the updated event(s) with the bot parameters attached.

<APIPage document={"./openapi.json"} operations={[{"path":"/calendar_events/{uuid}/bot","method":"post"}]} />


---

## Unschedule Record Event

### Source: ./content/docs/api/reference/calendars/unschedule_record_event.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Cancels a previously scheduled recording for a calendar event and releases associated bot resources. For recurring events, the 'all_occurrences' parameter controls whether to unschedule from all instances of the recurring series or just the specific occurrence. This operation is idempotent and will not error if no bot was scheduled. Returns the updated event(s) with the bot parameters removed.

<APIPage document={"./openapi.json"} operations={[{"path":"/calendar_events/{uuid}/bot","method":"delete"}]} />

---

## Update Calendar

### Source: ./content/docs/api/reference/calendars/update_calendar.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Updates a calendar integration with new credentials or platform while maintaining the same UUID. This operation is performed as an atomic transaction to ensure data integrity. The system automatically unschedules existing bots to prevent duplicates, updates the calendar credentials, and triggers a full resync of all events. Useful when OAuth tokens need to be refreshed or when migrating a calendar between providers. Returns the updated calendar object with its new configuration.

<APIPage document={"./openapi.json"} operations={[{"path":"/calendars/{uuid}","method":"patch"}]} />


---

## Bot Webhook Events Documentation

### Source: ./content/docs/api/reference/webhooks/bot_webhook_documentation.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Meeting BaaS sends the following webhook events related to bot recordings.

## Bot Webhook Event Types

### 1. `complete`
Sent when a bot successfully completes recording a meeting.

**Payload Structure:**
```json
{
  \"event\": \"complete\",
  \"data\": {
    \"bot_id\": \"123e4567-e89b-12d3-a456-426614174000\",
    \"event_uuid\": \"123e4567-e89b-12d3-a456-426614174001\",
    \"transcript\": [
      {
        \"speaker\": \"John Doe\",
        \"offset\": 1.5,
        \"start_time\": 1.5,
        \"end_time\": 2.4,
        \"words\": [
          {
            \"start\": 1.5,
            \"end\": 1.9,
            \"word\": \"Hello\"
          },
          {
            \"start\": 2.0,
            \"end\": 2.4,
            \"word\": \"everyone\"
          }
        ]
      }
    ],
    \"speakers\": [
      \"John Doe\",
      \"Jane Smith\"
    ],
    \"mp4\": \"https://storage.example.com/recordings/video123.mp4?token=abc\",
    \"audio\": \"https://storage.example.com/recordings/audio123.wav?token=abc\",
    \"event\": \"complete\",
    \"extra\": {
      \"foo\": \"bar\"
    }
  }
}
```

**When it's triggered:**
- After a bot successfully records and processes a meeting
- After the recording is uploaded and made available
- When all processing of the meeting recording is complete

**What to do with it:**
- Download the MP4 recording for storage in your system
- Store the transcript data in your database
- Update meeting status in your application
- Notify users that the recording is available
- Use `event_uuid` to correlate with calendar events (if applicable)

### 2. `failed`
Sent when a bot fails to join or record a meeting.

**Payload Structure:**
```json
{
  \"event\": \"failed\",
  \"data\": {
    \"bot_id\": \"123e4567-e89b-12d3-a456-426614174000\",
    \"event_uuid\": \"123e4567-e89b-12d3-a456-426614174001\",
    \"error\": \"meeting_not_found\",
    \"message\": \"Could not join meeting: The meeting ID was not found or has expired\",
    \"extra\": {
      \"foo\": \"bar\"
    }
  }
}
```

**Common error types:**
- `meeting_not_found`: The meeting ID or link was invalid or expired
- `access_denied`: The bot was denied access to the meeting
- `authentication_error`: Failed to authenticate with the meeting platform
- `network_error`: Network connectivity issues during recording
- `internal_error`: Internal server error

**What to do with it:**
- Log the failure for troubleshooting
- Notify administrators or users about the failed recording
- Attempt to reschedule if appropriate
- Update meeting status in your system
- Use `event_uuid` to correlate with calendar events (if applicable)

### 3. `transcription_complete`
Sent when transcription is completed separately from recording.

**Payload Structure:**
```json
{
  \"event\": \"transcription_complete\",
  \"data\": {
    \"bot_id\": \"123e4567-e89b-12d3-a456-426614174000\"
  }
}
```

**When it's triggered:**
- After requesting retranscription via the API
- When an asynchronous transcription job completes
- When a higher quality or different language transcription becomes available

**What to do with it:**
- Update the transcript data in your system
- Notify users that improved transcription is available
- Run any post-processing on the new transcript data

## Webhook Usage Tips

- Each event includes the `bot_id` so you can correlate with your internal data
- The `event_uuid` field is included when the bot was created from a calendar event (null for direct bots or scheduled bots)
- The complete event includes speaker identification and full transcript data
- For downloading recordings, the mp4 URL is valid for 24 hours
- Handle the webhook asynchronously and return 200 OK quickly to prevent timeouts

For security, always validate the API key in the `x-meeting-baas-api-key` header matches your API key.

<APIPage document={"./openapi.json"} operations={[{"path":"/bots/webhooks/bot","method":"get"}]} />

---

## Calendar Webhook Events Documentation

### Source: ./content/docs/api/reference/webhooks/calendar_webhook_documentation.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Meeting BaaS sends the following webhook events related to calendar integrations.

## Calendar Webhook Event Types

### 1. `calendar.sync_events`
Sent when calendar events are synced with external providers.

**Payload Structure:**
```json
{
  \"event\": \"calendar.sync_events\",
  \"data\": {
    \"calendar_id\": \"123e4567-e89b-12d3-a456-426614174000\",
    \"last_updated_ts\": \"2023-05-01T12:00:00Z\",
    \"affected_event_uuids\": [
      \"123e4567-e89b-12d3-a456-426614174001\",
      \"123e4567-e89b-12d3-a456-426614174002\"
    ]
  }
}
```

**When it's triggered:**
- After initial calendar connection is established
- When external calendar providers (Google, Microsoft) send change notifications
- After manual calendar resync operations
- During scheduled periodic syncs
- When events are created, updated, or deleted in the source calendar

**What to do with it:**
- Update your local copy of calendar events
- Process any new events that match your criteria
- Remove any deleted events from your system
- Update schedules for any modified events
- Refresh your UI to show the latest calendar data

**Field details:**
- `calendar_id`: The UUID of the synchronized calendar
- `last_updated_ts`: ISO-8601 timestamp when the sync occurred
- `affected_event_uuids**: Array of UUIDs for events that were changed

## Integration with Meeting BaaS Calendar API

After receiving a calendar webhook event, you can:
1. Use the `/calendar_events` endpoint to retrieve detailed information about specific events
2. Use the `/calendars/:uuid` endpoint to get calendar metadata
3. Schedule recording bots for any new meetings with the `/calendar_events/:uuid/bot` endpoint

## Webhook Usage Tips

- Each event includes affected event UUIDs for efficient processing
- You don't need to retrieve all calendar events - just process the changed ones
- The timestamp helps determine the sequence of updates
- For high-frequency calendars, consider batch processing of multiple events

For security, always validate the API key in the `x-meeting-baas-api-key` header matches your API key.

<APIPage document={"./openapi.json"} operations={[{"path":"/bots/webhooks/calendar","method":"get"}]} />

---

## Webhook Events Documentation

### Source: ./content/docs/api/reference/webhooks/webhook_documentation.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Meeting BaaS sends webhook events to your configured webhook URL when specific events occur.

## Webhook Event Types

### 1. `complete`
Sent when a bot successfully completes recording a meeting. Contains full transcription data and a link to the recording.
```json
{
  \"event\": \"complete\",
  \"data\": {
    \"bot_id\": \"123e4567-e89b-12d3-a456-426614174000\",
    \"event_uuid\": \"123e4567-e89b-12d3-a456-426614174001\",
    \"transcript\": [
      {
        \"speaker\": \"John Doe\",
        \"offset\": 1.5,
        \"start_time\": 1.5,
        \"end_time\": 2.4,
        \"words\": [
          {
            \"start\": 1.5,
            \"end\": 1.9,
            \"word\": \"Hello\"
          },
          {
            \"start\": 2.0,
            \"end\": 2.4,
            \"word\": \"everyone\"
          }
        ]
      }
    ],
    \"speakers\": [
      \"Jane Smith\",
      \"John Doe\"
    ],
    \"mp4\": \"https://storage.example.com/recordings/video123.mp4?token=abc\",
    \"audio\": \"https://storage.example.com/recordings/audio123.wav?token=abc\",
    \"event\": \"complete\",
    \"extra\": {
      \"foo\": \"bar\"
    }
  }
}
```

The `complete` event includes:
- **bot_id**: Unique identifier for the bot that completed recording
- **event_uuid**: UUID of the calendar event (if this bot was created from an event)
- **speakers**: A set of speaker names identified in the meeting
- **transcript**: Full transcript data with speaker identification and word timing
- **mp4**: URL to the recording file (valid for 24 hours by default)
- **event**: Event type identifier ("complete")

### 2. `failed`
Sent when a bot fails to join or record a meeting. Contains error details.
```json
{
  \"event\": \"failed\",
  \"data\": {
    \"bot_id\": \"123e4567-e89b-12d3-a456-426614174000\",
    \"event_uuid\": \"123e4567-e89b-12d3-a456-426614174001\",
    \"error\": \"meeting_not_found\",
    \"message\": \"Could not join meeting: The meeting ID was not found or has expired\",
    \"extra\": {
      \"foo\": \"bar\"
    }
  }
}
```

The `failed` event includes:
- **bot_id**: Unique identifier for the bot that failed
- **event_uuid**: UUID of the calendar event (if this bot was created from an event)
- **error**: Error code identifying the type of failure
- **message**: Detailed human-readable error message

Common error types include:
- `meeting_not_found`: The meeting ID or link was invalid or expired
- `access_denied`: The bot was denied access to the meeting
- `authentication_error`: Failed to authenticate with the meeting platform
- `network_error`: Network connectivity issues during recording
- `internal_error`: Internal server error

### 3. `calendar.sync_events`
Sent when calendar events are synced. Contains information about which events were updated.
```json
{
  \"event\": \"calendar.sync_events\",
  \"data\": {
    \"calendar_id\": \"123e4567-e89b-12d3-a456-426614174000\",
    \"last_updated_ts\": \"2023-05-01T12:00:00Z\",
    \"affected_event_uuids\": [
      \"123e4567-e89b-12d3-a456-426614174001\",
      \"123e4567-e89b-12d3-a456-426614174002\"
    ]
  }
}
```

The `calendar.sync_events` event includes:
- **calendar_id**: UUID of the calendar that was synced
- **last_updated_ts**: ISO-8601 timestamp of when the sync occurred
- **affected_event_uuids**: Array of UUIDs for calendar events that were added, updated, or deleted

This event is triggered when:
- Calendar data is synced with the external provider (Google, Microsoft)
- Multiple events may be created, updated, or deleted in a single sync operation
- Use this event to update your local cache of calendar events

### 4. `transcription_complete`
Sent when transcription is completed separately from recording (e.g., after retranscribing).
```json
{
  \"event\": \"transcription_complete\",
  \"data\": {
    \"bot_id\": \"123e4567-e89b-12d3-a456-426614174000\"
  }
}
```

The `transcription_complete` event includes:
- **bot_id**: Unique identifier for the bot with the completed transcription

This event is sent when:
- You request a retranscription via the `/bots/retranscribe` endpoint
- An asynchronous transcription process completes after the recording has ended

## Setting Up Webhooks

You can configure webhooks in two ways:
1. **Account-level webhook URL**: Set a default webhook URL for all bots in your account using the `/accounts/webhook_url` endpoint
2. **Bot-specific webhook URL**: Provide a `webhook_url` parameter when creating a bot with the `/bots` endpoint

Your webhook endpoint must:
- Accept POST requests with JSON payload
- Return a 2xx status code to acknowledge receipt
- Process requests within 10 seconds to avoid timeouts
- Handle each event type appropriately based on the event type

All webhook requests include:
- `x-meeting-baas-api-key` header with your API key for verification
- `content-type: application/json` header
- JSON body containing the event details

## Webhook Reliability

If your endpoint fails to respond or returns an error, the system will attempt to retry the webhook delivery. For critical events, we recommend implementing:

- Idempotency handling to prevent duplicate processing of the same event
- Proper logging of webhook receipts for audit purposes
- Asynchronous processing to quickly acknowledge receipt before handling the event data

For security, always validate the API key in the `x-meeting-baas-api-key` header matches your API key.

<APIPage document={"./openapi.json"} operations={[{"path":"/bots/webhooks","method":"get"}]} />

---

## Create Zoom OAuth Connection

### Source: ./content/docs/api/reference/zoom-oauth/create_zoom_oauth_connection.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Exchanges a Zoom OAuth authorization code for access and refresh tokens, retrieves the Zoom user's profile, and stores the connection for managed OBF token generation. The authorization code is obtained by directing a Zoom user through the OAuth consent flow for your Zoom OAuth app. Once stored, you can reference this connection's `zoom_user_id` as the `zoom_obf_token_user_id` parameter when creating a bot, and the system will automatically fetch a fresh OBF token at join time. Note: the authorization code is single-use and expires in approximately 10 minutes.

<APIPage document={"./openapi.json"} operations={[{"path":"/zoom_oauth_connections/","method":"post"}]} />


---

## Delete Zoom OAuth Connection

### Source: ./content/docs/api/reference/zoom-oauth/delete_zoom_oauth_connection.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Permanently deletes a Zoom OAuth connection by its UUID, removing all stored tokens. After deletion, bots using this connection's `zoom_user_id` as `zoom_obf_token_user_id` will no longer be able to automatically fetch OBF tokens. The Zoom user would need to re-authorize to create a new connection.

<APIPage document={"./openapi.json"} operations={[{"path":"/zoom_oauth_connections/{uuid}","method":"delete"}]} />

---

## Get Zoom OAuth Connection

### Source: ./content/docs/api/reference/zoom-oauth/get_zoom_oauth_connection.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieves a specific Zoom OAuth connection by its UUID. Returns the connection details including the Zoom user ID, account ID, connection state, and granted scopes. Sensitive token data is never included in the response.

<APIPage document={"./openapi.json"} operations={[{"path":"/zoom_oauth_connections/{uuid}","method":"get"}]} />


---

## List Zoom OAuth Connections

### Source: ./content/docs/api/reference/zoom-oauth/list_zoom_oauth_connections.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Retrieves all Zoom OAuth connections associated with the authenticated account. Each connection represents a Zoom user who has authorized your app via OAuth. Use this to display connected users or to find the `zoom_user_id` needed for the `zoom_obf_token_user_id` bot parameter. Sensitive token data is never included in the response.

<APIPage document={"./openapi.json"} operations={[{"path":"/zoom_oauth_connections/","method":"get"}]} />

---

## Calendar Management Tools

Tools for managing calendar integrations and synchronization with Google and Microsoft calendars

### Source: ./content/docs/mcp-servers/chat-mcp/tools/calendar-management.mdx


This section covers the tools available for managing calendar integrations in your Meeting BaaS implementation. These tools enable you to create, manage, and synchronize calendar integrations for automated meeting recordings and bot scheduling.

## Available Tools

### createCalendar

Creates a new calendar integration for your Meeting BaaS instance.

#### Use Cases
- Setting up automatic meeting recordings
- Configuring calendar-based bot scheduling
- Enabling recurring meeting coverage

#### Parameters
- `oauthClientId` (string): OAuth client ID for authentication with the calendar service
- `oauthClientSecret` (string): OAuth client secret for secure authentication
- `oauthRefreshToken` (string): OAuth refresh token for maintaining persistent access
- `platform` (enum): Calendar service provider, must be either "Google" or "Microsoft"
- `rawCalendarId` (string, optional): Specific calendar ID to integrate. If not provided, defaults to primary calendar

#### Response
- Success: Returns a confirmation message indicating successful calendar creation
- Error: Returns an error message if creation fails

#### Example
```typescript
try {
  const response = await server.tools.createCalendar({
    oauthClientId: "your_client_id",
    oauthClientSecret: "your_client_secret",
    oauthRefreshToken: "your_refresh_token",
    platform: "Google",
    rawCalendarId: "primary"
  });
  console.log("Calendar created successfully");
} catch (error) {
  console.error("Failed to create calendar:", error);
}
```

### listCalendars

Retrieves a list of all configured calendar integrations in your system.

#### Use Cases
- Viewing all configured calendars
- Checking calendar integration status
- Managing multiple calendar integrations

#### Parameters
None required

#### Response
- Success: Returns a JSON object containing all configured calendars
- Error: Returns an error message if listing fails

#### Example
```typescript
try {
  const response = await server.tools.listCalendars();
  console.log("Configured calendars:", response);
} catch (error) {
  console.error("Failed to list calendars:", error);
}
```

### getCalendar

Retrieves detailed information about a specific calendar integration.

#### Use Cases
- Viewing specific calendar configuration
- Checking individual calendar status
- Verifying calendar settings

#### Parameters
- `calendarId` (string): The unique identifier of the calendar to retrieve

#### Response
- Success: Returns detailed information about the requested calendar
- Error: Returns an error message if retrieval fails

#### Example
```typescript
try {
  const response = await server.tools.getCalendar({
    calendarId: "calendar-123-xyz"
  });
  console.log("Calendar details:", response);
} catch (error) {
  console.error("Failed to get calendar:", error);
}
```

### deleteCalendar

Removes a calendar integration from your system.

#### Use Cases
- Removing unwanted calendar connections
- Stopping automatic recordings for specific calendars
- Cleaning up calendar integration data

#### Parameters
- `calendarId` (string): The unique identifier of the calendar to delete

#### Response
- Success: Returns a confirmation of calendar deletion
- Error: Returns an error message if deletion fails

#### Example
```typescript
try {
  const response = await server.tools.deleteCalendar({
    calendarId: "calendar-123-xyz"
  });
  console.log("Calendar deleted successfully");
} catch (error) {
  console.error("Failed to delete calendar:", error);
}
```

### updateCalendar

Updates an existing calendar integration's configuration.

#### Use Cases
- Modifying calendar settings
- Updating connection details
- Changing calendar configuration

#### Parameters
- `calendarId` (string): The unique identifier of the calendar to update
- `oauthClientId` (string): Updated OAuth client ID for authentication
- `oauthClientSecret` (string): Updated OAuth client secret for secure authentication
- `oauthRefreshToken` (string): Updated OAuth refresh token for maintaining persistent access
- `platform` (enum): Calendar service provider, must be either "Google" or "Microsoft"

#### Response
- Success: Returns a confirmation message indicating successful calendar update
- Error: Returns an error message if update fails

#### Example
```typescript
try {
  const response = await server.tools.updateCalendar({
    calendarId: "calendar-123-xyz",
    oauthClientId: "updated_client_id",
    oauthClientSecret: "updated_client_secret",
    oauthRefreshToken: "updated_refresh_token",
    platform: "Google"
  });
  console.log("Calendar updated successfully");
} catch (error) {
  console.error("Failed to update calendar:", error);
}
```

### resyncAllCalendars

Forces a synchronization of all configured calendar integrations.

#### Use Cases
- Updating calendar data manually
- Fixing synchronization issues
- Refreshing all calendar connections

#### Parameters
None required

#### Response
- Success: Returns a confirmation of successful resynchronization
- Error: Returns an error message if resync fails

#### Example
```typescript
try {
  const response = await server.tools.resyncAllCalendars();
  console.log("All calendars resynced successfully");
} catch (error) {
  console.error("Failed to resync calendars:", error);
}
```

## Error Handling

All calendar management tools include comprehensive error handling. It's recommended to implement try-catch blocks when using these tools:

```typescript
try {
  // Calendar operation
  const response = await server.tools.calendarOperation(params);
  // Handle success
} catch (error) {
  console.error("Calendar operation failed:", error);
  // Handle error appropriately
}
```

---

## Event Management Tools

Tools for managing meeting events and recordings

### Source: ./content/docs/mcp-servers/chat-mcp/tools/event-management.mdx


This section covers the comprehensive suite of tools available for managing meeting events, including scheduling recordings, managing calendar events, and handling automated bot activities.

## Available Tools

### listEvents

Lists all scheduled events from a specified calendar. This tool is particularly useful when you need to:
- View upcoming recordings
- Check scheduled transcriptions
- Monitor planned bot activities

#### Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `calendarId` | string | Yes | The unique identifier of the calendar to list events from |

#### Response Format
The tool returns a structured response containing the list of events:

```typescript
interface EventResponse {
  content: Array<{
    type: string;
    text: string; // JSON stringified event data
  }>;
  isError?: boolean;
}
```

#### Example Usage
```typescript
try {
  const response = await server.tools.listEvents({
    calendarId: "calendar-123-xyz"
  });
  
  // Response will contain list of events
  console.log(response.content[0].text);
} catch (error) {
  // Handle error
}
```

### scheduleRecordEvent

Configures automatic recording for a specific calendar event. Use this tool when you need to:
- Set up automatic recording for meetings
- Schedule future transcriptions
- Plan meeting recordings with specific configurations

#### Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `eventUuid` | string | Yes | Unique identifier of the event to be recorded |
| `botName` | string | Yes | Name of the recording bot that will handle the session |
| `extra` | object | No | Additional configuration parameters for recording |
| `allOccurrences` | boolean | No | Whether to schedule recording for all instances of a recurring event |

#### Extra Configuration Options
The `extra` parameter can include various recording configurations:
```typescript
{
  quality: "high" | "medium" | "low",
  transcription: boolean,
  // Additional configuration options as needed
}
```

#### Example Usage
```typescript
try {
  const response = await server.tools.scheduleRecordEvent({
    eventUuid: "event-123-xyz",
    botName: "Recording Bot",
    extra: {
      quality: "high",
      transcription: true
    },
    allOccurrences: false
  });
  
  // Handle successful scheduling
  console.log(response.content[0].text);
} catch (error) {
  // Handle error
}
```

### unscheduleRecordEvent

Cancels previously scheduled recordings for calendar events. This tool is useful when you need to:
- Cancel automatic recording
- Stop planned transcription
- Remove scheduled bot activity

#### Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `eventUuid` | string | Yes | Unique identifier of the event |
| `allOccurrences` | boolean | No | Whether to cancel recordings for all instances of a recurring event |

#### Example Usage
```typescript
try {
  const response = await server.tools.unscheduleRecordEvent({
    eventUuid: "event-123-xyz",
    allOccurrences: false
  });
  
  // Handle successful cancellation
  console.log(response.content[0].text);
} catch (error) {
  // Handle error
}
```

## Error Handling

All event management tools include comprehensive error handling. Here's the recommended pattern:

```typescript
try {
  const response = await server.tools.eventOperation(params);
  if (response.isError) {
    // Handle error response
    console.error(response.content[0].text);
    return;
  }
  // Handle success
  console.log(response.content[0].text);
} catch (error) {
  // Handle unexpected errors
  console.error("Operation failed:", error);
}
```

## Response Structure

All tools return responses in a consistent format:

```typescript
interface EventResponse {
  content: Array<{
    type: string;
    text: string;
  }>;
  isError?: boolean;
}
```

### Success Response Example
```json
{
  "content": [
    {
      "type": "text",
      "text": "Successfully completed the operation"
    }
  ]
}
```

### Error Response Example
```json
{
  "content": [
    {
      "type": "text",
      "text": "Operation failed: Detailed error message"
    }
  ],
  "isError": true
}
```

---

## Meeting Management Tools

Comprehensive guide for managing meetings and bot interactions in the Meeting Control Platform

### Source: ./content/docs/mcp-servers/chat-mcp/tools/meeting-management.mdx


The Meeting Management Tools provide a robust set of functionalities for controlling and managing meeting operations through AI bots. These tools enable seamless integration of AI assistants into meetings, allowing them to join, participate, collect data, and manage meeting resources effectively.

## Available Tools

### 1. joinSpeaking

**Purpose**: Enables an AI bot to join a meeting with full speaking capabilities, allowing real-time voice interaction.

#### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `meetingUrl` | string | Yes | The URL of the meeting to join |
| `botName` | string | Yes | Display name for the bot in the meeting |
| `extra` | object | No | Additional configuration options |

#### Example Usage

```typescript
try {
  const response = await server.tools.joinSpeaking({
    meetingUrl: "https://meet.example.com/123",
    botName: "Assistant Bot",
    extra: {
      role: "note-taker",
      capabilities: ["transcription", "recording"]
    }
  });
  console.log("Bot joined successfully:", response);
} catch (error) {
  console.error("Failed to join meeting:", error);
}
```

#### Response
The tool returns a response object containing:
- `botId`: Unique identifier for the bot instance
- `status`: Current connection status
- `joinedAt`: Timestamp of when the bot joined

### 2. leaveMeeting

**Purpose**: Gracefully removes an AI bot from an active meeting, ensuring proper cleanup of resources.

#### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `botId` | string | Yes | Unique identifier of the bot to remove |

#### Example Usage

```typescript
try {
  const response = await server.tools.leaveMeeting({
    botId: "bot-123-xyz"
  });
  console.log("Bot left successfully:", response);
} catch (error) {
  console.error("Failed to leave meeting:", error);
}
```

### 3. getMeetingData

**Purpose**: Retrieves comprehensive meeting data including transcriptions, recordings, and bot status information.

#### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `botId` | string | Yes | Bot identifier to fetch associated meeting data |

#### Example Usage

```typescript
try {
  const meetingData = await server.tools.getMeetingData({
    botId: "bot-123-xyz"
  });
  console.log("Meeting data retrieved:", meetingData);
} catch (error) {
  console.error("Failed to fetch meeting data:", error);
}
```

#### Response Structure
```typescript
interface MeetingData {
  meetingId: string;
  status: 'active' | 'ended';
  duration: number;
  participants: number;
  transcription?: {
    text: string;
    timestamp: number;
  }[];
  recording?: {
    url: string;
    duration: number;
    format: string;
  };
}
```

### 4. deleteData

**Purpose**: Permanently removes all data associated with a specific bot instance, including recordings and transcriptions.

#### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `botId` | string | Yes | Identifier of the bot whose data should be deleted |

#### Example Usage

```typescript
try {
  await server.tools.deleteData({
    botId: "bot-123-xyz"
  });
  console.log("Data deleted successfully");
} catch (error) {
  console.error("Failed to delete data:", error);
}
```

### 5. botsWithMetadata

**Purpose**: Retrieves a comprehensive list of all active bots and their associated metadata.

#### Parameters
This tool takes no parameters.

#### Example Usage

```typescript
try {
  const botsList = await server.tools.botsWithMetadata();
  console.log("Active bots:", botsList);
} catch (error) {
  console.error("Failed to fetch bots:", error);
}
```

#### Response Structure
```typescript
interface BotMetadata {
  botId: string;
  name: string;
  status: 'active' | 'inactive';
  joinedAt: string;
  meetingUrl: string;
  capabilities: string[];
}
```

## Error Handling

All tools implement robust error handling with specific error types:

```typescript
try {
  const result = await server.tools.someOperation(params);
  // Handle success case
} catch (error) {
  if (error instanceof MeetingConnectionError) {
    // Handle connection issues
    console.error("Connection failed:", error.message);
  } else if (error instanceof AuthenticationError) {
    // Handle authentication issues
    console.error("Authentication failed:", error.message);
  } else {
    // Handle other types of errors
    console.error("Operation failed:", error);
  }
}
```

---

## Speaking Bot Tools

A comprehensive guide to managing AI speaking bots in your meetings

### Source: ./content/docs/mcp-servers/chat-mcp/tools/speaking-bot.mdx


Speaking Bot Tools provide a powerful interface for integrating AI-powered voice participants into your meetings. These tools enable you to create interactive, voice-capable AI bots that can join your video meetings, adopt specific personas, and engage in real-time conversations.

## Core Functionality

### Available Personas

The speaking bot system offers a diverse range of personas to suit different meeting contexts and requirements. Each persona comes with its unique communication style, expertise, and personality traits.

<Accordions type="single">
  <Accordion title="Browse Available Personas">
    <div className="grid grid-cols-2 gap-4 md:grid-cols-3">
      <div className="space-y-2">
        <h4 className="font-medium">Technical Roles</h4>
        <ul className="list-disc pl-4">
          <li>C++ Veteran</li>
          <li>Golang Minimalist</li>
          <li>Grafana Guru</li>
          <li>Haskell Purist</li>
          <li>Lisp Enlightened</li>
          <li>Pair Programmer</li>
          <li>Rust Evangelist</li>
        </ul>
      </div>
      <div className="space-y-2">
        <h4 className="font-medium">Business Roles</h4>
        <ul className="list-disc pl-4">
          <li>BaaS Onboarder</li>
          <li>Corporate Girlboss</li>
          <li>Data Baron</li>
          <li>Factory Patriarch</li>
          <li>Hospital Administrator</li>
          <li>Interviewer</li>
        </ul>
      </div>
      <div className="space-y-2">
        <h4 className="font-medium">Specialty Roles</h4>
        <ul className="list-disc pl-4">
          <li>Academic Warlord</li>
          <li>Climate Engineer</li>
          <li>Deep Sea Therapist</li>
          <li>Futuristic AI Philosopher</li>
          <li>Military Strategist</li>
          <li>Quantum Physicist</li>
        </ul>
      </div>
    </div>
    
    <details>
      <summary className="mt-4 cursor-pointer text-sm text-gray-600">View Complete Persona List</summary>
      <ul className="mt-2 columns-2 md:columns-3 list-disc pl-4">
        {/* Original complete list of personas */}
        <li>1940s Noir Detective</li>
        <li>Ancient Alien Theorist</li>
        <li>Ancient Roman General</li>
        <li>Arctic Prospector</li>
        {/* ... rest of the personas ... */}
      </ul>
    </details>
  </Accordion>
</Accordions>

## API Reference

### joinSpeakingMeeting

Creates and sends an AI speaking bot to join a video meeting.

#### Request Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `meetingUrl` | string | Yes | The URL of the meeting to join |
| `botName` | string | Yes | Display name for the bot in the meeting |
| `meetingBaasApiKey` | string | Yes | Your MeetingBaas API authentication key |
| `personas` | string[] | No | Array of preferred personas (first available will be used) |
| `botImage` | string | No | Custom avatar URL for the bot |
| `entryMessage` | string | No | Initial message when joining |
| `enableTools` | boolean | No | Enable bot tools (default: true) |
| `extra` | object | No | Additional custom configuration |

#### Example Usage

```typescript
const response = await server.tools.joinSpeakingMeeting({
  meetingUrl: 'https://meet.example.com/123',
  botName: 'AI Assistant',
  meetingBaasApiKey: process.env.MEETING_BAAS_API_KEY,
  personas: ['pair_programmer', 'tech_support'],
  botImage: 'https://example.com/bot-avatar.png',
  entryMessage: "Hello! I'm here to assist with the meeting.",
  enableTools: true,
  extra: {
    role: 'technical_assistant',
    specialization: 'code_review'
  }
});
```

#### Response Structure

```typescript
interface JoinResponse {
  content: Array<{
    type: string;
    text: string; // Contains the bot ID
  }>;
  isError?: boolean;
}
```

### leaveSpeakingMeeting

Removes a speaking bot from an active meeting.

#### Request Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `botId` | string | Yes | The unique ID of the bot to remove |
| `meetingBaasApiKey` | string | Yes | Your MeetingBaas API authentication key |

#### Example Usage

```typescript
const response = await server.tools.leaveSpeakingMeeting({
  botId: 'bot-123-xyz',
  meetingBaasApiKey: process.env.MEETING_BAAS_API_KEY
});
```

## Implementation Guide

### Error Handling

Implement robust error handling to manage potential issues:

```typescript
try {
  const response = await server.tools.joinSpeakingMeeting(params);
  console.log('Bot joined successfully:', response.content[0].text);
} catch (error) {
  console.error('Failed to join meeting:', error);
  // Implement appropriate error recovery
}
```

## Complete Integration Example

Here's a comprehensive example showing how to integrate the speaking bot tools:

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { config } from 'dotenv';

// Load environment variables
config();

// Initialize server
const server = new McpServer();

async function manageSpeakingBot() {
  let botId;
  
  try {
    // Join meeting
    const joinResponse = await server.tools.joinSpeakingMeeting({
      meetingUrl: 'https://meet.example.com/123',
      botName: 'AI Assistant',
      meetingBaasApiKey: process.env.MEETING_BAAS_API_KEY,
      personas: ['pair_programmer'],
      entryMessage: 'Ready to assist with the meeting!'
    });

    // Extract bot ID from response
    botId = joinResponse.content[0].text.split(': ')[1];
    
    // Set up cleanup handler
    process.on('SIGINT', async () => {
      if (botId) {
        await cleanupBot(botId);
        process.exit(0);
      }
    });

  } catch (error) {
    console.error('Failed to manage bot:', error);
    if (botId) {
      await cleanupBot(botId);
    }
  }
}

async function cleanupBot(botId: string) {
  try {
    await server.tools.leaveSpeakingMeeting({
      botId,
      meetingBaasApiKey: process.env.MEETING_BAAS_API_KEY
    });
    console.log('Bot cleanup successful');
  } catch (error) {
    console.error('Bot cleanup failed:', error);
  }
}
```

## API Endpoints Reference

The speaking bot tools interact with the following endpoints:

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/bots` | POST | Create and deploy a new speaking bot |
| `/bots/{botId}` | DELETE | Remove an active speaking bot |

## Response Types

### Success Response

```json
{
  "content": [
    {
      "type": "text",
      "text": "Successfully joined meeting with speaking bot ID: bot-123-xyz"
    }
  ]
}
```

### Error Response

```json
{
  "content": [
    {
      "type": "text",
      "text": "Failed to join meeting with speaking bot: Invalid meeting URL"
    }
  ],
  "isError": true
}
```


---

## Utility Tools

Essential utility tools for testing, debugging, and system maintenance

### Source: ./content/docs/mcp-servers/chat-mcp/tools/utility-tools.mdx


The MCP (Model Context Protocol) server provides a set of utility tools designed to facilitate testing, debugging, and basic system operations. These tools are essential for developers to ensure proper functionality and maintain their MCP server implementations.

## Available Tools

### Echo Tool

The Echo tool is a fundamental utility that provides a simple way to verify server connectivity and test basic functionality. It reflects back any message sent to it, making it ideal for testing and debugging purposes.

#### Purpose and Use Cases

1. **Server Verification**
   - Test initial server setup and configuration
   - Verify server responsiveness
   - Debug communication channels

2. **Health Monitoring**
   - Implement basic health checks
   - Monitor server latency
   - Validate message passing functionality

3. **Development and Testing**
   - Debug message formatting
   - Test error handling
   - Verify client-server communication

#### Technical Specification

##### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `message` | string | Yes | The message to be echoed back by the server |

##### Response Format

The Echo tool returns a response in the following structure:

```typescript
interface EchoResponse {
  content: Array<{
    type: "text";
    text: string;  // Format: "Tool echo: {message}"
  }>;
}
```

#### Implementation Guide

Below is a complete implementation example of the Echo tool using the MCP SDK:

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp";
import { z } from "zod";

export function registerEchoTool(server: McpServer): McpServer {
  server.tool(
    "echo",
    "Echo back the provided message for testing purposes",
    { message: z.string() },
    async ({ message }: { message: string }) => ({
      content: [
        {
          type: "text",
          text: `Tool echo: ${message}`,
        },
      ],
    })
  );

  return server;
}
```

#### Usage Examples

1. **Basic Echo Test**
```typescript
const response = await server.tools.echo({
  message: "Hello, MCP!"
});

// Expected Response:
// {
//   "content": [
//     {
//       "type": "text",
//       "text": "Tool echo: Hello, MCP!"
//     }
//   ]
// }
```

2. **Integration Example**
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp";

// Initialize MCP server
const server = new McpServer();

// Register the echo tool
registerEchoTool(server);

// Example usage with error handling
async function testEchoTool() {
  try {
    const response = await server.tools.echo({
      message: "Testing MCP server connection"
    });
    
    console.log("Echo response:", response.content[0].text);
    return response;
  } catch (error) {
    console.error("Echo test failed:", error);
    throw error;
  }
}

// Execute the test
testEchoTool()
  .then(() => console.log("Echo test completed successfully"))
  .catch(() => console.log("Echo test failed"));
```

---

## Calendar Tools

Comprehensive APIs and tools for managing calendar integrations with Meeting BaaS, including Google Calendar and Microsoft Calendar support

### Source: ./content/docs/mcp-servers/meeting-mcp/tools/calender-tools.mdx


Calendar Tools provide a robust set of APIs for integrating and managing calendar systems within your application. These tools support both Google Calendar and Microsoft Calendar integrations through OAuth authentication.

## OAuth Setup Guide

### oauthGuidance

Get detailed step-by-step instructions for setting up OAuth authentication with Google or Microsoft calendars.

```typescript
GET /api/calendar/oauth/guidance
```

**Response:**
- Comprehensive setup instructions for both Google and Microsoft OAuth
- Required API permissions and scopes
- Step-by-step credential creation process
- Security best practices

## Calendar Integration

### listRawCalendars

Lists all available calendars from Google or Microsoft before integration.

```typescript
POST /api/calendar/raw/list
```

**Parameters:**
```json
{
  "platform": "Google | Microsoft",
  "clientId": "your_client_id",
  "clientSecret": "your_client_secret",
  "refreshToken": "your_refresh_token"
}
```

**Response:**
```json
{
  "calendars": [
    {
      "id": "calendar_id",
      "name": "Calendar Name",
      "isPrimary": true,
      "email": "calendar@example.com"
    }
  ]
}
```

### setupCalendarOAuth

Integrates a calendar using OAuth credentials.

```typescript
POST /api/calendar/setup
```

**Parameters:**
```json
{
  "platform": "Google | Microsoft",
  "clientId": "your_client_id",
  "clientSecret": "your_client_secret",
  "refreshToken": "your_refresh_token",
  "rawCalendarId": "optional_calendar_id" // Optional
}
```

**Response:**
```json
{
  "success": true,
  "calendarId": "uuid",
  "name": "Calendar Name",
  "email": "calendar@example.com"
}
```

## Calendar Management

### listCalendars

Lists all integrated calendars in your system.

```typescript
GET /api/calendars
```

**Response:**
```json
{
  "calendars": [
    {
      "uuid": "calendar_uuid",
      "name": "Calendar Name",
      "email": "calendar@example.com",
      "platform": "Google | Microsoft",
      "lastSynced": "2024-03-21T10:00:00Z"
    }
  ]
}
```

### getCalendar

Retrieves detailed information about a specific calendar.

```typescript
GET /api/calendars/{calendarId}
```

**Parameters:**
- `calendarId`: UUID of the calendar (path parameter)

**Response:**
```json
{
  "uuid": "calendar_uuid",
  "name": "Calendar Name",
  "email": "calendar@example.com",
  "platform": "Google | Microsoft",
  "lastSynced": "2024-03-21T10:00:00Z",
  "settings": {
    "timezone": "UTC",
    "defaultReminders": []
  }
}
```

### deleteCalendar

Removes a calendar integration from your system.

```typescript
DELETE /api/calendars/{calendarId}
```

**Parameters:**
- `calendarId`: UUID of the calendar (path parameter)

**Response:**
```json
{
  "success": true,
  "message": "Calendar successfully deleted"
}
```

### resyncAllCalendars

Forces a refresh of all connected calendars.

```typescript
POST /api/calendars/resync
```

**Response:**
```json
{
  "success": true,
  "syncedCalendars": 5,
  "lastSyncTime": "2024-03-21T10:00:00Z"
}
```

## Event Management

### listUpcomingMeetings

Retrieves upcoming meetings from a specific calendar.

```typescript
GET /api/calendars/{calendarId}/meetings
```

**Parameters:**
- `calendarId`: UUID of the calendar (path parameter)
- `status`: "upcoming" | "past" | "all" (query parameter, optional)
- `limit`: number (query parameter, optional)

**Response:**
```json
{
  "meetings": [
    {
      "id": "meeting_id",
      "title": "Meeting Title",
      "startTime": "2024-03-21T10:00:00Z",
      "endTime": "2024-03-21T11:00:00Z",
      "isRecorded": false
    }
  ]
}
```

### listEvents

Lists calendar events with comprehensive filtering options.

```typescript
GET /api/calendars/{calendarId}/events
```

**Parameters:**
- `calendarId`: UUID of the calendar (path parameter)
- `startDateGte`: ISO date string (query parameter, optional)
- `startDateLte`: ISO date string (query parameter, optional)
- `attendeeEmail`: string (query parameter, optional)
- Additional filters available

**Response:**
```json
{
  "events": [
    {
      "id": "event_id",
      "title": "Event Title",
      "description": "Event Description",
      "startTime": "2024-03-21T10:00:00Z",
      "endTime": "2024-03-21T11:00:00Z",
      "attendees": [
        {
          "email": "attendee@example.com",
          "responseStatus": "accepted"
        }
      ],
      "meetingLink": "https://meet.example.com/123"
    }
  ]
}
```

### listEventsWithCredentials

Similar to listEvents but accepts direct API credentials.

```typescript
GET /api/calendars/{calendarId}/events/direct
```

**Parameters:**
- Same as listEvents, plus:
- `apiKey`: Your API key (header)

### getEvent

Retrieves detailed information about a specific calendar event.

```typescript
GET /api/events/{eventId}
```

**Parameters:**
- `eventId`: UUID of the event (path parameter)

**Response:**
```json
{
  "id": "event_id",
  "title": "Event Title",
  "description": "Event Description",
  "startTime": "2024-03-21T10:00:00Z",
  "endTime": "2024-03-21T11:00:00Z",
  "attendees": [],
  "recordingStatus": "scheduled | recording | completed | none"
}
```

## Recording Management

### scheduleRecording

Schedules a bot to record an upcoming meeting.

```typescript
POST /api/events/{eventId}/record
```

**Parameters:**
```json
{
  "botName": "Recording Bot",
  "botImage": "https://example.com/bot-avatar.png", // Optional
  "recordingMode": "speaker | gallery | automatic", // Optional
  "quality": "high | medium | low" // Optional
}
```

**Response:**
```json
{
  "success": true,
  "recordingId": "recording_uuid",
  "scheduledStartTime": "2024-03-21T10:00:00Z"
}
```

### scheduleRecordingWithCredentials

Similar to scheduleRecording but accepts direct API credentials.

```typescript
POST /api/events/{eventId}/record/direct
```

**Parameters:**
- Same as scheduleRecording, plus:
- `apiKey`: Your API key (header)

### cancelRecording

Cancels a previously scheduled recording.

```typescript
DELETE /api/events/{eventId}/record
```

**Parameters:**
- `eventId`: UUID of the event (path parameter)
- `allOccurrences`: boolean (query parameter, optional)

**Response:**
```json
{
  "success": true,
  "message": "Recording cancelled successfully"
}
```

### cancelRecordingWithCredentials

Similar to cancelRecording but accepts direct API credentials.

```typescript
DELETE /api/events/{eventId}/record/direct
```

**Parameters:**
- Same as cancelRecording, plus:
- `apiKey`: Your API key (header)

## System Health

### checkCalendarIntegration

Diagnoses calendar integration status and health.

```typescript
GET /api/calendar/health
```

**Response:**
```json
{
  "status": "healthy | degraded | error",
  "lastSync": "2024-03-21T10:00:00Z",
  "connectedCalendars": 5,
  "activeRecordings": 2,
  "issues": [
    {
      "type": "auth_error | sync_error | api_error",
      "message": "Detailed error message",
      "calendarId": "affected_calendar_uuid"
    }
  ],
  "recommendations": [
    "List of troubleshooting steps or recommendations"
  ]
}
```

## Best Practices

1. **OAuth Token Management**
   - Securely store refresh tokens
   - Implement token rotation
   - Handle token expiration gracefully

2. **Error Handling**
   - Implement proper error handling for API rate limits
   - Handle calendar sync conflicts
   - Manage recording failures appropriately

3. **Performance Optimization**
   - Cache calendar data when appropriate
   - Implement pagination for large event lists
   - Use webhook notifications for calendar updates

4. **Security Considerations**
   - Use HTTPS for all API calls
   - Implement proper authentication
   - Regular security audits
   - Monitor for suspicious activities

## Rate Limits

- Standard tier: 100 requests per minute
- Enterprise tier: 1000 requests per minute
- Webhook notifications: 50 per second

## Support

For additional support or questions:
- Documentation: https://docs.example.com/calendar-tools
- Support Email: support@example.com
- API Status: https://status.example.com


---

## Link Sharing Tools

Tools and APIs for creating and managing shareable links to meeting recordings and segments, enabling easy sharing of meeting content and timestamps

### Source: ./content/docs/mcp-servers/meeting-mcp/tools/link-sharing-tools.mdx


These tools help you create well-formatted, shareable links to meeting recordings and segments, making it easier to reference and share specific parts of meetings with your team.

## shareableMeetingLink

Creates a beautifully formatted, shareable link to a meeting recording with rich metadata that can be directly shared in chat applications.

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `botId` | string | Yes | The unique identifier for the meeting bot |
| `timestamp` | string | No | Timestamp in format "HH:MM:SS" to link to a specific moment |
| `title` | string | No | Title of the meeting |
| `speakerName` | string | No | Name of the current speaker |
| `description` | string | No | Brief description of the meeting or segment |

### Returns
A markdown-formatted string containing the meeting link with metadata that can be shared in chat applications.

### Example Usage

```typescript
const link = await shareableMeetingLink({
  botId: "abc123",
  timestamp: "00:12:35",
  title: "Weekly Team Sync",
  speakerName: "Sarah Johnson",
  description: "Discussing the new product roadmap"
});
```

### Output Format
```markdown
📽️ **Meeting Recording: Weekly Team Sync**
⏱️ Timestamp: 00:12:35
🎤 Speaker: Sarah Johnson
📝 Discussing the new product roadmap

🔗 [View Recording](https://meetingbaas.com/viewer/abc123?t=755)
```

## shareMeetingSegments

Generates a formatted list of links to multiple important moments in a meeting, perfect for creating a table of contents or highlighting key discussions.

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `botId` | string | Yes | The unique identifier for the meeting bot |
| `segments` | Segment[] | Yes | Array of meeting segments |

#### Segment Object Structure
```typescript
interface Segment {
  timestamp: string;        // Format: "HH:MM:SS"
  speaker?: string;        // Optional speaker name
  description: string;     // Description of the segment
  title?: string;         // Optional segment title
}
```

### Returns
A markdown-formatted list of segments with direct links to each moment in the meeting.

### Example Usage

```typescript
const segments = await shareMeetingSegments({
  botId: "abc123",
  segments: [
    {
      timestamp: "00:00:00",
      title: "Meeting Start",
      speaker: "John Doe",
      description: "Introduction and agenda overview"
    },
    {
      timestamp: "00:15:30",
      title: "Q1 Results",
      speaker: "Jane Smith",
      description: "Financial performance review"
    },
    {
      timestamp: "00:45:20",
      title: "Product Updates",
      speaker: "Mike Johnson",
      description: "New feature announcements"
    }
  ]
});
```

### Output Format
```markdown
## Meeting Segments

1. 🎯 **Meeting Start** (00:00:00)
   👤 John Doe
   📝 Introduction and agenda overview
   🔗 [Jump to segment](https://meetingbaas.com/viewer/abc123?t=0)

2. 📊 **Q1 Results** (00:15:30)
   👤 Jane Smith
   📝 Financial performance review
   🔗 [Jump to segment](https://meetingbaas.com/viewer/abc123?t=930)

3. 🚀 **Product Updates** (00:45:20)
   👤 Mike Johnson
   📝 New feature announcements
   🔗 [Jump to segment](https://meetingbaas.com/viewer/abc123?t=2720)
```

## Best Practices

1. **Timestamps**: Always use the HH:MM:SS format for consistency
2. **Descriptions**: Keep descriptions concise but informative
3. **Titles**: Use clear, descriptive titles that indicate the content
4. **Segments**: When creating segments, ensure they follow a logical flow
5. **Speaker Names**: Use full names for better clarity and searchability

## Tips for Effective Link Sharing

- Use timestamps strategically to point to the exact moment of important discussions
- Include relevant context in descriptions to help viewers understand the content
- Group related segments together when sharing multiple links
- Consider your audience when writing descriptions and choosing segments to share
- Use titles that make it easy to find specific content later


---

## Meeting Tools

Comprehensive APIs and tools for managing automated meeting bots, including recording, transcription, and real-time audio streaming capabilities

### Source: ./content/docs/mcp-servers/meeting-mcp/tools/meeting-tools.mdx


Meeting Tools provide a powerful set of APIs for managing automated meeting bots that can join, record, and transcribe video conferences. These tools enable seamless integration of meeting automation capabilities into your applications.

## Core Features
- Automated meeting recording
- Real-time transcription
- Customizable bot appearance and behavior
- Support for multiple speech-to-text providers
- Audio streaming capabilities
- Meeting data retrieval and management

## API Reference

### createBot

Creates an intelligent meeting bot that can join video conferences to record and transcribe meetings.

```typescript
interface CreateBotParams {
  meeting_url: string;              // Required: URL of the meeting to join
  name?: string;                    // Optional: Custom name for the bot
  botImage?: string;               // Optional: URL to bot's avatar image
  entryMessage?: string;           // Optional: Message bot sends when joining
  deduplicationKey?: string;       // Optional: Override 5-minute same meeting restriction
  nooneJoinedTimeout?: number;     // Optional: Timeout (seconds) if no one joins
  waitingRoomTimeout?: number;     // Optional: Timeout (seconds) if stuck in waiting room
  speechToTextProvider?: 'Gladia' | 'Runpod' | 'Default';  // Optional: Transcription provider
  speechToTextApiKey?: string;     // Optional: API key for speech-to-text service
  streamingInputUrl?: string;      // Optional: WebSocket URL for audio input
  streamingOutputUrl?: string;     // Optional: WebSocket URL for audio output
  streamingAudioFrequency?: '16khz' | '24khz';  // Optional: Audio streaming frequency
  extra?: {                        // Optional: Additional meeting metadata
    meetingType?: string;
    customSummaryPrompt?: string;
    searchKeywords?: string[];
    [key: string]: any;
  };
}
```

**Returns:**
```typescript
interface BotResponse {
  botId: string;
  status: 'joined' | 'waiting' | 'failed';
  meetingId: string;
  joinTime: string;
}
```

### getBots

Retrieves a list of all active bots and their associated meetings.

**Returns:**
```typescript
interface BotsResponse {
  bots: Array<{
    botId: string;
    meetingId: string;
    status: string;
    joinTime: string;
    meetingUrl: string;
  }>;
}
```

### getBotsByMeeting

Retrieves all bots associated with a specific meeting URL.

```typescript
interface GetBotsByMeetingParams {
  meetingUrl: string;  // Required: URL of the meeting to query
}
```

**Returns:**
```typescript
interface MeetingBotsResponse {
  bots: Array<{
    botId: string;
    status: string;
    joinTime: string;
  }>;
}
```

### getRecording

Retrieves detailed recording information for a specific bot/meeting.

```typescript
interface GetRecordingParams {
  botId: string;  // Required: ID of the bot that made the recording
}
```

**Returns:**
```typescript
interface RecordingResponse {
  recordingId: string;
  duration: number;
  status: 'in-progress' | 'completed' | 'failed';
  downloadUrl?: string;
  createdAt: string;
}
```

### getRecordingStatus

Checks the current status of an in-progress recording.

```typescript
interface RecordingStatusParams {
  recordingId: string;  // Required: ID of the recording to check
}
```

**Returns:**
```typescript
interface RecordingStatusResponse {
  status: 'in-progress' | 'completed' | 'failed';
  progress?: number;
  errorMessage?: string;
}
```

### getMeetingData

Retrieves comprehensive transcript and recording data for a specific meeting.

```typescript
interface GetMeetingDataParams {
  meetingId: string;  // Required: ID of the meeting to retrieve data for
}
```

**Returns:**
```typescript
interface MeetingDataResponse {
  meetingId: string;
  duration: number;
  transcriptSegments: number;
  participants: string[];
  recording: {
    downloadUrl: string;
    size: number;
    format: string;
  };
  transcript: {
    segments: Array<{
      speaker: string;
      text: string;
      startTime: number;
      endTime: number;
    }>;
  };
}
```

### getMeetingDataWithCredentials

Retrieves meeting data using direct API authentication credentials.

```typescript
interface GetMeetingDataWithCredentialsParams {
  meetingId: string;  // Required: ID of the meeting
  apiKey: string;     // Required: API key for authentication
}
```

**Returns:** Same as `getMeetingDataResponse`

## Best Practices

1. **Bot Naming**: Use descriptive names that help identify the bot's purpose (e.g., "Sales-Meeting-Recorder").

2. **Timeouts**: Set appropriate timeout values based on your meeting context:
   - `nooneJoinedTimeout`: Recommended 300 seconds (5 minutes)
   - `waitingRoomTimeout`: Recommended 180 seconds (3 minutes)

3. **Speech-to-Text Providers**:
   - Default: Best for general purpose transcription
   - Gladia: Optimized for multiple languages
   - Runpod: Best for high-accuracy technical content

4. **Audio Streaming**:
   - Use 16kHz for standard quality
   - Use 24kHz for high-quality audio requirements

5. **Deduplication**:
   - Use `deduplicationKey` when you need multiple bots in the same meeting
   - Generate unique keys to bypass the 5-minute restriction

## Error Handling

Common error scenarios and recommended handling:

```typescript
interface ErrorResponse {
  error: {
    code: string;
    message: string;
    details?: any;
  };
}
```

- `MEETING_NOT_FOUND`: Verify the meeting URL is correct and accessible
- `BOT_JOIN_FAILED`: Check waiting room settings and meeting permissions
- `INVALID_CREDENTIALS`: Verify API key and authentication
- `RECORDING_FAILED`: Check storage capacity and network connectivity

## Rate Limits

- Standard tier: 10 requests per minute
- Premium tier: 100 requests per minute
- Enterprise tier: Custom limits available

## Security Considerations

1. Always store API keys securely
2. Use environment variables for sensitive credentials
3. Implement proper access controls for recording downloads
4. Monitor bot activity for unauthorized access
5. Regular audit of active bots and recordings

## Examples

### Creating a Basic Meeting Bot

```typescript
const response = await createBot({
  meeting_url: "https://meeting-url.com/123",
  name: "Meeting Recorder",
  nooneJoinedTimeout: 300,
  speechToTextProvider: "Default"
});
```

### Advanced Bot with Custom Configuration

```typescript
const response = await createBot({
  meeting_url: "https://meeting-url.com/123",
  name: "Sales Meeting Bot",
  botImage: "https://your-domain.com/bot-avatar.png",
  entryMessage: "Hello! I'm here to record the meeting.",
  speechToTextProvider: "Gladia",
  speechToTextApiKey: "your-api-key",
  extra: {
    meetingType: "sales",
    customSummaryPrompt: "Focus on action items and deal values",
    searchKeywords: ["proposal", "pricing", "follow-up"]
  }
});
```


---

## QR Code Tools

Tools and APIs for generating customizable, AI-powered QR codes that can be used as bot avatars or for sharing meeting links and contact information

### Source: ./content/docs/mcp-servers/meeting-mcp/tools/qr-code-tools.mdx


The `generateQRCode` tool creates AI-powered, visually appealing QR codes that can be used as bot avatars or for general purposes. These QR codes are not just functional but also aesthetically pleasing, combining art with utility.

### Parameters

| Parameter | Type | Description | Required |
|-----------|------|-------------|-----------|
| `type` | string | The type of QR code content. Options: `url`, `email`, `phone`, `sms`, `text` | Yes |
| `to` | string | The destination content for the QR code (e.g., URL, email address, phone number, or text) | Yes |
| `prompt` | string | AI generation prompt to customize the QR code's appearance (max 1000 characters) | Yes |
| `style` | string | Visual style of the QR code. Options: `style_default`, `style_dots`, `style_rounded`, `style_crystal` | Yes |
| `useAsBotImage` | boolean | Whether to set the generated QR code as the bot's avatar image (default: `true`) | No |
| `template` | string | Template ID for pre-defined QR code designs (optional) | No |
| `apiKey` | string | Your QR Code AI API key. If not provided, system default will be used | No |

### API Key Integration

You can provide your API key in two ways:
1. Through the `apiKey` parameter
2. Directly in the prompt by including phrases like "API key: qrc_your_key" or "Using API key: qrc_your_key"

### Returns

- A URL to the generated QR code image
- The image URL is compatible with other tools like `joinMeeting`
- The generated QR code is both scannable and visually appealing

### Style Guide

Each style option offers unique visual characteristics:
- `style_default`: Classic QR code appearance with AI-enhanced elements
- `style_dots`: Circular patterns for a modern, softer look
- `style_rounded`: Smooth corners and flowing design
- `style_crystal`: Crystalline structure with reflective effects

### Examples

#### Basic Usage
```bash
Generate a QR code with my email lazare@spoke.app that looks like a Tiger in crystal style
```

#### With API Key in Prompt
```bash
Generate a QR code for my website https://example.com that looks like a mountain landscape. Use API key: qrc_my-personal-api-key-123456
```

#### Formal Parameter Structure
```bash
Generate a QR code with the following parameters:
- Type: email
- To: john.doe@example.com
- Prompt: Create a QR code that looks like a mountain landscape
- Style: style_rounded
- API Key: qrc_my-personal-api-key-123456
```

### Best Practices

1. **Prompts**
   - Be specific about the desired visual elements
   - Include color preferences if any
   - Mention any specific themes or moods

2. **Testing**
   - Always test the QR code with multiple devices
   - Ensure sufficient contrast for scanning
   - Verify the encoded information is correct

3. **Design Considerations**
   - Choose styles that match your brand identity
   - Consider the scanning environment
   - Balance aesthetics with functionality

### Limitations

- Maximum prompt length: 1000 characters
- Image format: PNG
- Resolution: Up to 1024x1024 pixels
- API rate limits may apply based on your key type

### Security Notes

- Never share your API key publicly
- Use environment variables for API keys in production
- Regularly rotate API keys for security

For more information and support, visit our [API documentation](https://docs.qrcode-ai.com).


---

## Tools

Available tools and example workflows for Meeting MCP

### Source: ./content/docs/mcp-servers/meeting-mcp/tools/tools.mdx


## Available Tools

The Meeting MCP server exposes several tools through the MCP protocol:

<Accordions>
  <Accordion title="Calendar Tools" icon={<Calendar />} defaultOpen>
    - **oauthGuidance**: Get step-by-step instructions for setting up OAuth
    - **listRawCalendars**: List available calendars before integration
    - **setupCalendarOAuth**: Integrate a calendar using OAuth credentials
    - **listCalendars**: List all integrated calendars
    - **getCalendar**: Get detailed information about a specific calendar
    - **deleteCalendar**: Remove a calendar integration
    - **resyncAllCalendars**: Force a refresh of all connected calendars
    - **listUpcomingMeetings**: List upcoming meetings from a calendar
    - **listEvents**: List calendar events with filtering options
    - **getEvent**: Get detailed information about a specific event
    - **scheduleRecording**: Schedule a bot to record an upcoming meeting
    - **cancelRecording**: Cancel a previously scheduled recording
    - **checkCalendarIntegration**: Diagnose calendar integration issues
  </Accordion>

  <Accordion title="Meeting Tools" icon={<Video />}>
    - **createBot**: Create a meeting bot that can join video conferences
    - **getBots**: List all bots and their associated meetings
    - **getBotsByMeeting**: Get bots for a specific meeting URL
    - **getRecording**: Retrieve recording information
    - **getRecordingStatus**: Check the status of a recording
    - **getMeetingData**: Get transcript and recording data
  </Accordion>

  <Accordion title="Transcript Tools" icon={<FileText />}>
    - **getMeetingTranscript**: Get a complete meeting transcript with speaker information
    - **findKeyMoments**: Automatically identify important moments in a meeting
  </Accordion>

  <Accordion title="QR Code Tools" icon={<QrCode />}>
    - **generateQRCode**: Create an AI-generated QR code image for use as a bot avatar
  </Accordion>

  <Accordion title="Link Sharing Tools" icon={<Link />}>
    - **shareableMeetingLink**: Generate a formatted, shareable link to a recording
    - **shareMeetingSegments**: Create links to multiple important moments
  </Accordion>
</Accordions>

## QR Code API Key Configuration

The QR code generator tool requires an API key from [QR Code AI API](https://www.qrcode-ai-api.com/). There are several ways to provide this:

1. **Directly in the prompt**: Include your API key in the prompt when using the `generateQRCode` tool
2. **As a parameter**: Provide your API key as the `apiKey` parameter
3. **Environment variable**: Set the `QRCODE_API_KEY` environment variable
4. **Claude Desktop config**: Add the API key to your Claude Desktop configuration file

## Example Workflows

<Tabs>
  <Tab title="Recording a Meeting" value="recording">
```
# Create a bot for a meeting
"Create a bot for my Zoom meeting at https://zoom.us/j/123456789"

# Check recording status
"What's the status of my meeting recording for the Zoom call I started earlier?"
```
  </Tab>

  <Tab title="Calendar Integration" value="calendar">
```
# Get OAuth guidance
"I want to integrate my Google Calendar. How do I get OAuth credentials?"

# Set up calendar integration
"Integrate my Google Calendar using these credentials:
- Platform: Google
- Client ID: my-client-id-123456789.apps.googleusercontent.com
- Client Secret: my-client-secret-ABCDEF123456
- Refresh Token: my-refresh-token-ABCDEF123456789
- Raw Calendar ID: primary@gmail.com"

# View upcoming meetings
"Show me my upcoming meetings from calendar 1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"

# Schedule recording
"Schedule a recording for my team meeting with event ID 7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d"
```
  </Tab>

  <Tab title="Analyzing Content" value="analyzing">
```
# Get a meeting transcript
"Get the transcript from my team meeting with bot ID abc-123"

# Find key moments
"Identify key moments from yesterday's product planning meeting with bot ID xyz-456"

# Share a specific moment
"Create a shareable link to the part of meeting abc-123 at timestamp 12:45 where John was talking about the budget"
```
  </Tab>

  <Tab title="QR Code Generation" value="qrcode">
```
# Generate a QR code with contact information
"Generate a QR code with the following parameters:
- Type: email
- To: john.doe@company.com
- Prompt: Create a professional-looking QR code with abstract blue patterns
- Style: style_crystal"

# Use as a bot avatar
"Join my Zoom meeting at https://zoom.us/j/123456789 with the following parameters:
- Bot name: QR Code Assistant
- Bot image: [URL from the generated QR code]
- Entry message: Hello everyone, scan my avatar to get my contact information."
```
  </Tab>
</Tabs>


---

## Transcript Tools

Tools and APIs for managing meeting transcripts, including retrieval, analysis, speaker identification, and key moment extraction capabilities

### Source: ./content/docs/mcp-servers/meeting-mcp/tools/transcript-tools.mdx


Transcript Tools provide powerful capabilities for accessing, analyzing, and extracting insights from meeting recordings. These tools help you make the most of your meeting content through automated transcription and intelligent analysis.

## getMeetingTranscript

A powerful tool that retrieves complete meeting transcripts with speaker identification and organized content structure.

### Parameters

- `botId` (required): String - The unique identifier of the bot that recorded the meeting
- `format` (optional): String - Output format preference ("text" or "json", defaults to "text")

### Returns

Returns a formatted transcript that includes:
- Meeting metadata (title, duration, date)
- Complete conversation content organized by speaker
- Timestamps for each segment
- Speaker-separated paragraphs for improved readability

### Example Output

```text
Meeting: "Weekly Product Sync"
Date: 2024-03-15
Duration: 45m 30s

Transcript:

John Smith (09:00:00 AM):
Hello everyone, thanks for joining today's call. We have a lot to cover regarding 
the Q3 roadmap and our current progress on the platform redesign.

Sarah Johnson (09:00:45 AM):
Thanks John. I've prepared some slides about the user testing results we got back 
yesterday. The feedback was generally positive but there are a few areas we need 
to address.
```

### Usage Notes

- Transcripts are automatically processed for accuracy and clarity
- Speaker identification is based on voice recognition and meeting participant data
- Supports multiple output formats for integration with other tools
- Can handle meetings of any duration

## findKeyMoments

An AI-powered tool that automatically identifies and extracts significant moments from meeting recordings, making it easy to review and share important discussions.

### Parameters

- `botId` (required): String - The unique identifier of the bot that recorded the meeting
- `meetingTitle` (optional): String - Filter for a specific meeting by title
- `topics` (optional): Array String - List of specific topics to search for
- `maxMoments` (optional): Number - Maximum number of key moments to return (default: 10)
- `minConfidence` (optional): Number - Minimum confidence score for moment detection (0-1, default: 0.7)

### Returns

Returns a markdown-formatted list of key moments including:
- Timestamp with clickable links
- Context description
- Speaker information
- Confidence score
- Topic categorization

### Example Output

```markdown
## Key Moments - Product Strategy Meeting

1. 🎯 Product Roadmap Discussion [09:05:23]
   - Speaker: John Smith
   - "We're prioritizing the mobile experience for Q3, with a focus on 
     performance improvements"
   - Confidence: 0.95
   - [Jump to moment](meeting-link#t=545)

2. 📊 User Testing Results [09:15:45]
   - Speaker: Sarah Johnson
   - "Our latest usability tests showed an 85% satisfaction rate with the new UI"
   - Confidence: 0.89
   - [Jump to moment](meeting-link#t=945)
```

### Features

- **AI-Powered Analysis**: Uses advanced natural language processing to identify:
  - Decision points
  - Action items
  - Important announcements
  - Key discussions
  - Questions and answers

- **Smart Topic Detection**: Automatically categorizes moments into relevant topics
  - Product updates
  - Technical discussions
  - Team decisions
  - Action items
  - Project milestones

- **Customizable Detection**: 
  - Adjust sensitivity for moment detection
  - Filter by specific topics or speakers
  - Set custom importance thresholds

### Integration Capabilities

Both tools support integration with:
- Meeting platforms (Zoom, Teams, Google Meet)
- Project management tools
- Knowledge bases
- Team collaboration platforms

### Best Practices

1. **For Optimal Transcription**:
   - Ensure good audio quality
   - Ask speakers to identify themselves
   - Use noise-canceling when possible

2. **For Key Moment Detection**:
   - Provide relevant topics for more focused results
   - Set appropriate confidence thresholds
   - Review and validate automated selections

3. **For Data Management**:
   - Regularly archive transcripts
   - Set up appropriate access controls
   - Follow data retention policies


---

## Authentication

### Source: ./content/docs/transcript-seeker/concepts/api/authentication.mdx


Transcript Seeker requires authentication for calendar functionality, leveraging Better-Auth for secure access. Currently, it only supports Google Calendar with Google authentication, but Microsoft Calendar support may be added in a future update.

## Google Authentication

```js
console.log('Hello World');
```


---

## Database

### Source: ./content/docs/transcript-seeker/concepts/api/database.mdx


**Transcript Seeker requires a database connection to store user data.** This is essential for managing calendar integration, which uses Better-Auth to store user information securely.

The current setup utilizes Drizzle ORM for database management, where it will store information like user details, sessions, and more.

<Callout>
  This applies specifically to the calendar feature. Other user data is stored
  locally in the browser using PGLite. To learn more, visit our [web database
  documentation](/docs/transcript-seeker/concepts/web/database).
</Callout>

## Database Configuration

To get started, set up a PostgreSQL-compatible database. We recommend using Turso for this purpose. Follow this [guide](/docs/transcript-seeker/guides/turso) for detailed steps on setting up a Turso database.

## Database Migration

To run a database migration, use the following commands:

```bash
cd apps/api
pnpm db:push
```

## Database Studio

To visualize the data in a user-friendly interface, use the command below:

```bash
cd apps/api
pnpm db:studio
```


---

## Database

### Source: ./content/docs/transcript-seeker/concepts/web/database.mdx


**Transcript Seeker** uses a PGLite database to store data, which is essential for its functionality. PGLite enables us to run PostgreSQL in the browser, allowing secure storage of user data. We use **Drizzle ORM** with PGLite to handle data management efficiently.

<Callout>
  This setup is used to store meeting data, API keys, and more. To learn more
  about how authentication data is stored for calendars, visit [this
  page](/docs/transcript-seeker/concepts/api/database).
</Callout>

## Database Migration

To migrate the database after making changes, run the following commands:

```bash
pnpm db:generate
pnpm db:migrate
```

## Cleaning Migrations

To remove all migrations, use the following commands:

```bash
cd packages/db
rm -rf drizzle drizzle_ts
```


---

## Firebase

Learn how to deploy Transcript Seeker to Firebase.

### Source: ./content/docs/transcript-seeker/guides/deployment/firebase.mdx


## Setup

Before deploying to Firebase, you need to configure the `.env.production.local` files for each app.
Please follow this [guide](/docs/transcript-seeker/concepts/environment-variables) to set up environment variables.

Change the `NODE_ENV` to `production` in the terminal:

```bash title="Terminal"
export NODE_ENV="production"
```

### Firebase Hosting

For more information on Firebase Hosting, refer to the official [Firebase Hosting Documentation](https://firebase.google.com/docs/transcript-seeker/hosting).

<Steps>

<Step>

### Create a Firebase Project

If not already, create a Firebase project in the Firebase Console:

1. Create a project named **Transcript Seeker**.
2. Then, create a new web app:
   - `transcript-seeker-proxy`: Replace `transcript-seeker-proxy` in `firebase.json` and `.firebaserc` with this new ID.
3. Navigate to **Hosting** and click "Get Started."
4. Finally, Replace `transcript-seeker-bdc29` in `firebase.json` and `.firebaserc` with your project id. (This is for firebase hosting, when you click on get started it automatically creates a hosting project with ur app id)

</Step>

<Step>

### Install Firebase CLI Globally

Always ensure you have the latest version of the Firebase CLI installed.

<Tabs groupId='package-manager' persist items={['npm', 'pnpm', 'yarn']}>

```bash tab="npm"
npm install -g firebase-tools@latest
```

```bash tab="pnpm"
pnpm add -g firebase-tools@latest
```

```bash tab="yarn"
yarn global add firebase-tools@latest
```

</Tabs>

<Callout>
  {' '}
  Make sure to use version
  [^11.18.0](https://github.com/firebase/firebase-tools/releases/tag/v11.18.0)
  or higher to deploy `nodejs18` functions.{' '}
</Callout>

</Step>

<Step>

### Log in to Firebase

Use the Firebase CLI to log into your Firebase account.

```bash title="Terminal"
firebase login
```

</Step>

</Steps>

### Google Cloud Run

For more information on installation and the Google Cloud CLI, check out the official [Google Cloud CLI Documentation](https://cloud.google.com/sdk/docs/transcript-seeker/install#deb).

<Callout>
  If you're using the devcontainer configuration provided by this repository,
  the GCloud CLI will be automatically installed.
</Callout>

### Initialize Google Cloud CLI

This command will prompt you to log in and select the project you're working on.

```bash title="Terminal"
gcloud init
```

---

## Deployment

## Proxy Deployment

<Callout>
  You need to be on the **Blaze plan** to use Nitro with cloud functions.
</Callout>

<Steps>

<Step>

### Set Up Firebase Functions

In the Firebase Console:

1. Navigate to **Functions** and click "Get Started."

This completes the Firebase setup.

</Step>

<Step>
### Navigate to the Proxy Application Directory

```bash title="Terminal"
cd apps/proxy
```

</Step>

<Step>
### Build and Deploy

To deploy to Firebase Hosting, first build the Nitro app, then deploy:

```bash title="Terminal"
NITRO_PRESET=firebase pnpm build
firebase deploy
```

</Step>
</Steps>

---

## API Deployment

<Callout>
  The API cannot be deployed to Firebase functions. Instead, we are using Google
  Cloud Run.
</Callout>

<Steps>

<Step>
### Navigate to the Project Root

```bash title="Terminal"
cd /workspaces/transcript-seeker
```

</Step>

<Step>

### Create a Cloud Run Service

The following command will create a Cloud Run service. Modify the `SERVICE_NAME` to suit your needs.

```bash title="Terminal"
export DEPLOY_REGION="us-central1"
export SERVICE_NAME="transcript-seeker-api-prod"

gcloud run deploy "$SERVICE_NAME" \
  --image=us-docker.pkg.dev/cloudrun/container/hello \
  --region="$DEPLOY_REGION" \
  --allow-unauthenticated \
  --port=3001 \
  --set-env-vars "$(grep -v '^#' apps/api/.env.production.local | grep -v '^\s*$' | sed 's/=\s*"\(.*\)"$/=\1/' | tr '\n' ',' | sed 's/,$//')"
```

</Step>

<Step>

### Build and Deploy the Cloud Run Service

The command below builds and deploys the Cloud Run service. Modify the `SERVICE_NAME` and `GITHUB_USERNAME` to suit your needs.

<Callout>
  The below command assumes you are using a git repository. If you are not, you
  can replace `COMMIT_SHA` with a unique identifier.
</Callout>

<Callout>
  If you have already deployed the frontend and are encountering a CORS error,
  it may be due to the API being built for production. Once the build is
  complete, the CORS error should be resolved.
</Callout>

```bash title="Terminal"
export COMMIT_SHA=$(git rev-parse --short HEAD)
export DEPLOY_REGION="us-central1"
export SERVICE_NAME="transcript-seeker-api-prod"
export GITHUB_USERNAME="your_github_username"

gcloud builds submit \
  --region="$DEPLOY_REGION" \
  --config=cloudbuild.yaml \
  --substitutions=_GITHUB_USERNAME="$GITHUB_USERNAME",_DEPLOY_REGION="$DEPLOY_REGION",_SERVICE_NAME="$SERVICE_NAME",COMMIT_SHA="$COMMIT_SHA"
```

</Step>

</Steps>

---

## Frontend Deployment

<Steps>

<Step>
### Navigate to the Frontend Application Directory

```bash title="Terminal"
cd apps/web
```

</Step>

<Step>
### Build and Deploy

To deploy the frontend to Firebase Hosting, build the project and deploy:

```bash title="Terminal"
pnpm build
firebase deploy
```

</Step>
</Steps>
---


---

## Deployment

This guide will help you deploy Transcript Seeker to different environments.

### Source: ./content/docs/transcript-seeker/guides/deployment/index.mdx


Transcript Seeker can be deployed using several services, depending on your requirements. The steps for deploying to Firebase, Vercel, and other environments are provided in their respective guides. Choose the guide that best suits your deployment needs.

### Deployment Options

<Cards>

<Card icon={<Flame className="text-purple-300" />} title='Firebase'     href="/docs/transcript-seeker/guides/deployment/firebase"
>

Learn how to configure and set up Transcript Seeker to deploy to firebase.

</Card>

<Card icon={<Triangle className="text-purple-300" />} title='Vercel'     href="/docs/transcript-seeker/guides/deployment/vercel"
>

Learn how to configure and set up Transcript Seeker to deploy to vercel.

</Card>

</Cards>


---

## Vercel

Learn how to deploy Transcript Seeker to Vercel.

### Source: ./content/docs/transcript-seeker/guides/deployment/vercel.mdx


This page is a **Work In Progress**.


---

## Join Meeting

### Source: ./content/docs/speaking-bots/reference/bots/join_meeting_bots_post.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Create and deploy a speaking bot in a meeting.

Launches an AI-powered bot that joins a video meeting through MeetingBaas
and processes audio using Pipecat's voice AI framework.

<APIPage document={"./speaking-bots-openapi.json"} operations={[{"path":"/bots","method":"post"}]} />


---

## Leave Bot

### Source: ./content/docs/speaking-bots/reference/bots/leave_bot_bots__bot_id__delete.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Remove a bot from a meeting by its ID.

This will:
1. Call the MeetingBaas API to make the bot leave
2. Close WebSocket connections if they exist
3. Terminate the associated Pipecat process

<APIPage document={"./speaking-bots-openapi.json"} operations={[{"path":"/bots/{bot_id}","method":"delete"}]} />


---

## Generate Persona Image

### Source: ./content/docs/speaking-bots/reference/personas/generate_persona_image_personas_generate_image_post.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Generate an image for a persona using Replicate.

<APIPage document={"./speaking-bots-openapi.json"} operations={[{"path":"/personas/generate-image","method":"post"}]} />


---

## Health

### Source: ./content/docs/speaking-bots/reference/system/health_health_get.mdx


{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Health check endpoint

<APIPage document={"./speaking-bots-openapi.json"} operations={[{"path":"/health","method":"get"}]} />

---

