#
tokens: 35932/50000 2/34 files (page 2/2)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 2 of 2. Use http://codebase.md/meeting-baas/meeting-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .cursor
│   └── rules
│       ├── fastmcp.mdc
│       ├── joke.mdc
│       ├── mcp.mdc
│       ├── meetingbaas.mdc
│       └── nextjs.mdc
├── .editorconfig
├── .gitignore
├── .husky
│   └── pre-commit
├── .prettierignore
├── .prettierrc
├── FORMATTING.md
├── LICENSE
├── [email protected]
├── openapi.json
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   ├── cleanup_cursor_logs.sh
│   └── install-hooks.js
├── set_up_for_other_mcp.md
├── src
│   ├── api
│   │   └── client.ts
│   ├── config.ts
│   ├── index.ts
│   ├── resources
│   │   ├── index.ts
│   │   └── transcript.ts
│   ├── tools
│   │   ├── calendar.ts
│   │   ├── deleteData.ts
│   │   ├── environment.ts
│   │   ├── index.ts
│   │   ├── links.ts
│   │   ├── listBots.ts
│   │   ├── meeting.ts
│   │   ├── qrcode.ts
│   │   ├── retranscribe.ts
│   │   └── search.ts
│   ├── types
│   │   └── index.ts
│   └── utils
│       ├── auth.ts
│       ├── formatters.ts
│       ├── linkFormatter.ts
│       ├── logging.ts
│       ├── tinyDb.ts
│       └── tool-types.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/src/tools/calendar.ts:
--------------------------------------------------------------------------------

```typescript
   1 | /**
   2 |  * MCP tools for calendar integration
   3 |  *
   4 |  * This module provides a comprehensive set of tools for integrating with
   5 |  * Google and Microsoft calendars through the Meeting BaaS API. It includes
   6 |  * tools for OAuth setup, calendar management, event management, and recording scheduling.
   7 |  */
   8 | 
   9 | import type { Context, TextContent } from 'fastmcp';
  10 | import { z } from 'zod';
  11 | import { apiRequest } from '../api/client.js';
  12 | import { Calendar, CalendarEvent } from '../types/index.js';
  13 | 
  14 | // Define our session auth type
  15 | type SessionAuth = { apiKey: string };
  16 | 
  17 | // Define parameter schemas
  18 | const emptyParams = z.object({});
  19 | 
  20 | // Schema for OAuth setup and raw calendar listing
  21 | const oauthSetupParams = z.object({
  22 |   platform: z
  23 |     .enum(['Google', 'Microsoft'])
  24 |     .describe('The calendar provider platform (Google or Microsoft)'),
  25 |   clientId: z
  26 |     .string()
  27 |     .describe('OAuth client ID obtained from Google Cloud Console or Microsoft Azure portal'),
  28 |   clientSecret: z
  29 |     .string()
  30 |     .describe('OAuth client secret obtained from Google Cloud Console or Microsoft Azure portal'),
  31 |   refreshToken: z
  32 |     .string()
  33 |     .describe('OAuth refresh token obtained after user grants calendar access'),
  34 |   rawCalendarId: z
  35 |     .string()
  36 |     .optional()
  37 |     .describe(
  38 |       'Optional ID of specific calendar to integrate (from listRawCalendars). If not provided, the primary calendar is used',
  39 |     ),
  40 | });
  41 | 
  42 | const calendarIdParams = z.object({
  43 |   calendarId: z.string().uuid().describe('UUID of the calendar to query'),
  44 | });
  45 | 
  46 | const upcomingMeetingsParams = z.object({
  47 |   calendarId: z.string().uuid().describe('UUID of the calendar to query'),
  48 |   status: z
  49 |     .enum(['upcoming', 'past', 'all'])
  50 |     .optional()
  51 |     .default('upcoming')
  52 |     .describe("Filter for meeting status: 'upcoming' (default), 'past', or 'all'"),
  53 |   limit: z
  54 |     .number()
  55 |     .int()
  56 |     .min(1)
  57 |     .max(100)
  58 |     .optional()
  59 |     .default(20)
  60 |     .describe('Maximum number of events to return'),
  61 | });
  62 | 
  63 | const listEventsParams = z.object({
  64 |   calendarId: z.string().uuid().describe('UUID of the calendar to query'),
  65 |   status: z
  66 |     .enum(['upcoming', 'past', 'all'])
  67 |     .optional()
  68 |     .default('upcoming')
  69 |     .describe("Filter for meeting status: 'upcoming' (default), 'past', or 'all'"),
  70 |   startDateGte: z
  71 |     .string()
  72 |     .optional()
  73 |     .describe(
  74 |       "Filter events with start date ≥ this ISO-8601 timestamp (e.g., '2023-01-01T00:00:00Z')",
  75 |     ),
  76 |   startDateLte: z
  77 |     .string()
  78 |     .optional()
  79 |     .describe(
  80 |       "Filter events with start date ≤ this ISO-8601 timestamp (e.g., '2023-12-31T23:59:59Z')",
  81 |     ),
  82 |   attendeeEmail: z
  83 |     .string()
  84 |     .email()
  85 |     .optional()
  86 |     .describe('Filter events including this attendee email'),
  87 |   organizerEmail: z.string().email().optional().describe('Filter events with this organizer email'),
  88 |   updatedAtGte: z
  89 |     .string()
  90 |     .optional()
  91 |     .describe('Filter events updated at or after this ISO-8601 timestamp'),
  92 |   cursor: z.string().optional().describe('Pagination cursor from previous response'),
  93 | });
  94 | 
  95 | const eventIdParams = z.object({
  96 |   eventId: z.string().uuid().describe('UUID of the calendar event to query'),
  97 | });
  98 | 
  99 | const scheduleRecordingParams = z.object({
 100 |   eventId: z.string().uuid().describe('UUID of the calendar event to record'),
 101 |   botName: z.string().describe('Name to display for the bot in the meeting'),
 102 |   botImage: z.string().url().optional().describe("URL to an image for the bot's avatar (optional)"),
 103 |   entryMessage: z
 104 |     .string()
 105 |     .optional()
 106 |     .describe('Message the bot will send when joining the meeting (optional)'),
 107 |   recordingMode: z
 108 |     .enum(['speaker_view', 'gallery_view', 'audio_only'] as const)
 109 |     .default('speaker_view')
 110 |     .describe("Recording mode: 'speaker_view' (default), 'gallery_view', or 'audio_only'"),
 111 |   speechToTextProvider: z
 112 |     .enum(['Gladia', 'Runpod', 'Default'] as const)
 113 |     .optional()
 114 |     .describe('Provider for speech-to-text transcription (optional)'),
 115 |   speechToTextApiKey: z
 116 |     .string()
 117 |     .optional()
 118 |     .describe('API key for the speech-to-text provider if required (optional)'),
 119 |   extra: z
 120 |     .record(z.any())
 121 |     .optional()
 122 |     .describe('Additional metadata about the meeting (e.g., meetingType, participants)'),
 123 |   allOccurrences: z
 124 |     .boolean()
 125 |     .optional()
 126 |     .default(false)
 127 |     .describe(
 128 |       'For recurring events, schedule recording for all occurrences (true) or just this instance (false)',
 129 |     ),
 130 | });
 131 | 
 132 | const cancelRecordingParams = z.object({
 133 |   eventId: z.string().uuid().describe('UUID of the calendar event to cancel recording for'),
 134 |   allOccurrences: z
 135 |     .boolean()
 136 |     .optional()
 137 |     .default(false)
 138 |     .describe(
 139 |       'For recurring events, cancel recording for all occurrences (true) or just this instance (false)',
 140 |     ),
 141 | });
 142 | 
 143 | // Tool type with correct typing
 144 | type Tool<P extends z.ZodType<any, any>> = {
 145 |   name: string;
 146 |   description: string;
 147 |   parameters: P;
 148 |   execute: (
 149 |     args: z.infer<P>,
 150 |     context: Context<SessionAuth>,
 151 |   ) => Promise<string | { content: TextContent[] }>;
 152 | };
 153 | 
 154 | /**
 155 |  * List available calendars
 156 |  */
 157 | export const listCalendarsTool: Tool<typeof emptyParams> = {
 158 |   name: 'listCalendars',
 159 |   description: 'List all calendars integrated with Meeting BaaS',
 160 |   parameters: emptyParams,
 161 |   execute: async (_args, context) => {
 162 |     const { session, log } = context;
 163 |     log.info('Listing calendars');
 164 | 
 165 |     const response = await apiRequest(session, 'get', '/calendars/');
 166 | 
 167 |     if (response.length === 0) {
 168 |       return 'No calendars found. You can add a calendar using the setupCalendarOAuth tool.';
 169 |     }
 170 | 
 171 |     const calendarList = response
 172 |       .map((cal: Calendar) => `- ${cal.name} (${cal.email}) [ID: ${cal.uuid}]`)
 173 |       .join('\n');
 174 | 
 175 |     return `Found ${response.length} calendars:\n\n${calendarList}`;
 176 |   },
 177 | };
 178 | 
 179 | /**
 180 |  * Get calendar details
 181 |  */
 182 | export const getCalendarTool: Tool<typeof calendarIdParams> = {
 183 |   name: 'getCalendar',
 184 |   description: 'Get detailed information about a specific calendar integration',
 185 |   parameters: calendarIdParams,
 186 |   execute: async (args, context) => {
 187 |     const { session, log } = context;
 188 |     log.info('Getting calendar details', { calendarId: args.calendarId });
 189 | 
 190 |     const response = await apiRequest(session, 'get', `/calendars/${args.calendarId}`);
 191 | 
 192 |     return `Calendar Details:
 193 | Name: ${response.name}
 194 | Email: ${response.email}
 195 | Platform ID: ${response.google_id || response.microsoft_id}
 196 | UUID: ${response.uuid}
 197 | ${response.resource_id ? `Resource ID: ${response.resource_id}` : ''}`;
 198 |   },
 199 | };
 200 | 
 201 | /**
 202 |  * List raw calendars (before integration)
 203 |  */
 204 | export const listRawCalendarsTool: Tool<typeof oauthSetupParams> = {
 205 |   name: 'listRawCalendars',
 206 |   description: 'List available calendars from Google or Microsoft before integration',
 207 |   parameters: oauthSetupParams,
 208 |   execute: async (args, context) => {
 209 |     const { session, log } = context;
 210 |     log.info('Listing raw calendars', { platform: args.platform });
 211 | 
 212 |     const payload = {
 213 |       oauth_client_id: args.clientId,
 214 |       oauth_client_secret: args.clientSecret,
 215 |       oauth_refresh_token: args.refreshToken,
 216 |       platform: args.platform,
 217 |     };
 218 | 
 219 |     try {
 220 |       const response = await apiRequest(session, 'post', '/calendars/raw', payload);
 221 | 
 222 |       if (!response.calendars || response.calendars.length === 0) {
 223 |         return 'No calendars found. Please check your OAuth credentials.';
 224 |       }
 225 | 
 226 |       const calendarList = response.calendars
 227 |         .map((cal: any) => {
 228 |           const isPrimary = cal.is_primary ? ' (Primary)' : '';
 229 |           return `- ${cal.email}${isPrimary} [ID: ${cal.id}]`;
 230 |         })
 231 |         .join('\n');
 232 | 
 233 |       return `Found ${response.calendars.length} raw calendars. You can use the setupCalendarOAuth tool to integrate any of these:\n\n${calendarList}\n\nGuidance: Copy the ID of the calendar you want to integrate and use it as the rawCalendarId parameter in setupCalendarOAuth.`;
 234 |     } catch (error) {
 235 |       return `Error listing raw calendars: ${error instanceof Error ? error.message : String(error)}\n\nGuidance for obtaining OAuth credentials:\n\n1. For Google:\n   - Go to Google Cloud Console (https://console.cloud.google.com)\n   - Create a project and enable the Google Calendar API\n   - Create OAuth 2.0 credentials (client ID and secret)\n   - Set up consent screen with calendar scopes\n   - Use OAuth playground (https://developers.google.com/oauthplayground) to get a refresh token\n\n2. For Microsoft:\n   - Go to Azure Portal (https://portal.azure.com)\n   - Register an app in Azure AD\n   - Add Microsoft Graph API permissions for calendars\n   - Create a client secret\n   - Use a tool like Postman to get a refresh token`;
 236 |     }
 237 |   },
 238 | };
 239 | 
 240 | /**
 241 |  * Setup calendar OAuth integration
 242 |  */
 243 | export const setupCalendarOAuthTool: Tool<typeof oauthSetupParams> = {
 244 |   name: 'setupCalendarOAuth',
 245 |   description: 'Integrate a calendar using OAuth credentials',
 246 |   parameters: oauthSetupParams,
 247 |   execute: async (args, context) => {
 248 |     const { session, log } = context;
 249 |     log.info('Setting up calendar OAuth', { platform: args.platform });
 250 | 
 251 |     const payload: {
 252 |       oauth_client_id: string;
 253 |       oauth_client_secret: string;
 254 |       oauth_refresh_token: string;
 255 |       platform: 'Google' | 'Microsoft';
 256 |       raw_calendar_id?: string;
 257 |     } = {
 258 |       oauth_client_id: args.clientId,
 259 |       oauth_client_secret: args.clientSecret,
 260 |       oauth_refresh_token: args.refreshToken,
 261 |       platform: args.platform,
 262 |     };
 263 | 
 264 |     if (args.rawCalendarId) {
 265 |       payload.raw_calendar_id = args.rawCalendarId;
 266 |     }
 267 | 
 268 |     try {
 269 |       const response = await apiRequest(session, 'post', '/calendars/', payload);
 270 | 
 271 |       return `Calendar successfully integrated!\n\nDetails:
 272 | Name: ${response.calendar.name}
 273 | Email: ${response.calendar.email}
 274 | UUID: ${response.calendar.uuid}
 275 | 
 276 | You can now use this UUID to list events or schedule recordings.`;
 277 |     } catch (error) {
 278 |       return `Error setting up calendar: ${error instanceof Error ? error.message : String(error)}\n\nPlease verify your OAuth credentials. Here's how to obtain them:\n\n1. For Google Calendar:\n   - Visit https://console.cloud.google.com\n   - Create a project and enable Google Calendar API\n   - Configure OAuth consent screen\n   - Create OAuth client ID and secret\n   - Use OAuth playground to get refresh token\n\n2. For Microsoft Calendar:\n   - Visit https://portal.azure.com\n   - Register an application\n   - Add Microsoft Graph calendar permissions\n   - Create client secret\n   - Complete OAuth flow to get refresh token`;
 279 |     }
 280 |   },
 281 | };
 282 | 
 283 | /**
 284 |  * Delete calendar integration
 285 |  */
 286 | export const deleteCalendarTool: Tool<typeof calendarIdParams> = {
 287 |   name: 'deleteCalendar',
 288 |   description: 'Permanently remove a calendar integration',
 289 |   parameters: calendarIdParams,
 290 |   execute: async (args, context) => {
 291 |     const { session, log } = context;
 292 |     log.info('Deleting calendar', { calendarId: args.calendarId });
 293 | 
 294 |     try {
 295 |       await apiRequest(session, 'delete', `/calendars/${args.calendarId}`);
 296 | 
 297 |       return 'Calendar integration has been successfully removed. All associated events and scheduled recordings have been deleted.';
 298 |     } catch (error) {
 299 |       return `Error deleting calendar: ${error instanceof Error ? error.message : String(error)}`;
 300 |     }
 301 |   },
 302 | };
 303 | 
 304 | /**
 305 |  * Force resync of all calendars
 306 |  */
 307 | export const resyncAllCalendarsTool: Tool<typeof emptyParams> = {
 308 |   name: 'resyncAllCalendars',
 309 |   description: 'Force a resync of all connected calendars',
 310 |   parameters: emptyParams,
 311 |   execute: async (_args, context) => {
 312 |     const { session, log } = context;
 313 |     log.info('Resyncing all calendars');
 314 | 
 315 |     try {
 316 |       const response = await apiRequest(session, 'post', '/calendars/resync_all');
 317 | 
 318 |       const syncedCount = response.synced_calendars?.length || 0;
 319 |       const errorCount = response.errors?.length || 0;
 320 | 
 321 |       let result = `Calendar sync operation completed.\n\n${syncedCount} calendars synced successfully.`;
 322 | 
 323 |       if (errorCount > 0) {
 324 |         result += `\n\n${errorCount} calendars failed to sync:`;
 325 |         response.errors.forEach((error: any) => {
 326 |           result += `\n- Calendar ${error[0]}: ${error[1]}`;
 327 |         });
 328 |       }
 329 | 
 330 |       return result;
 331 |     } catch (error) {
 332 |       return `Error resyncing calendars: ${error instanceof Error ? error.message : String(error)}`;
 333 |     }
 334 |   },
 335 | };
 336 | 
 337 | /**
 338 |  * List upcoming meetings
 339 |  */
 340 | export const listUpcomingMeetingsTool: Tool<typeof upcomingMeetingsParams> = {
 341 |   name: 'listUpcomingMeetings',
 342 |   description: 'List upcoming meetings from a calendar',
 343 |   parameters: upcomingMeetingsParams,
 344 |   execute: async (args, context) => {
 345 |     const { session, log } = context;
 346 |     log.info('Listing upcoming meetings', {
 347 |       calendarId: args.calendarId,
 348 |       status: args.status,
 349 |     });
 350 | 
 351 |     const response = await apiRequest(
 352 |       session,
 353 |       'get',
 354 |       `/calendar_events/?calendar_id=${args.calendarId}&status=${args.status}`,
 355 |     );
 356 | 
 357 |     if (!response.data || response.data.length === 0) {
 358 |       return `No ${args.status} meetings found in this calendar.`;
 359 |     }
 360 | 
 361 |     const meetings = response.data.slice(0, args.limit);
 362 | 
 363 |     const meetingList = meetings
 364 |       .map((meeting: CalendarEvent) => {
 365 |         const startTime = new Date(meeting.start_time).toLocaleString();
 366 |         const hasBot = meeting.bot_param ? '🤖 Bot scheduled' : '';
 367 |         const meetingLink = meeting.meeting_url ? `Link: ${meeting.meeting_url}` : '';
 368 | 
 369 |         return `- ${meeting.name} [${startTime}] ${hasBot} ${meetingLink} [ID: ${meeting.uuid}]`;
 370 |       })
 371 |       .join('\n');
 372 | 
 373 |     let result = `${args.status.charAt(0).toUpperCase() + args.status.slice(1)} meetings:\n\n${meetingList}`;
 374 | 
 375 |     if (response.next) {
 376 |       result += `\n\nMore meetings available. Use 'cursor: ${response.next}' to see more.`;
 377 |     }
 378 | 
 379 |     return result;
 380 |   },
 381 | };
 382 | 
 383 | /**
 384 |  * List events with comprehensive filtering
 385 |  */
 386 | export const listEventsTool: Tool<typeof listEventsParams> = {
 387 |   name: 'listEvents',
 388 |   description: 'List calendar events with comprehensive filtering options',
 389 |   parameters: listEventsParams,
 390 |   execute: async (args, context) => {
 391 |     const { session, log } = context;
 392 |     log.info('Listing calendar events', {
 393 |       calendarId: args.calendarId,
 394 |       filters: args,
 395 |     });
 396 | 
 397 |     // Build the query parameters
 398 |     let queryParams = `calendar_id=${args.calendarId}`;
 399 |     if (args.status) queryParams += `&status=${args.status}`;
 400 |     if (args.startDateGte)
 401 |       queryParams += `&start_date_gte=${encodeURIComponent(args.startDateGte)}`;
 402 |     if (args.startDateLte)
 403 |       queryParams += `&start_date_lte=${encodeURIComponent(args.startDateLte)}`;
 404 |     if (args.attendeeEmail)
 405 |       queryParams += `&attendee_email=${encodeURIComponent(args.attendeeEmail)}`;
 406 |     if (args.organizerEmail)
 407 |       queryParams += `&organizer_email=${encodeURIComponent(args.organizerEmail)}`;
 408 |     if (args.updatedAtGte)
 409 |       queryParams += `&updated_at_gte=${encodeURIComponent(args.updatedAtGte)}`;
 410 |     if (args.cursor) queryParams += `&cursor=${encodeURIComponent(args.cursor)}`;
 411 | 
 412 |     try {
 413 |       const response = await apiRequest(session, 'get', `/calendar_events/?${queryParams}`);
 414 | 
 415 |       if (!response.data || response.data.length === 0) {
 416 |         return 'No events found matching your criteria.';
 417 |       }
 418 | 
 419 |       const eventList = response.data
 420 |         .map((event: CalendarEvent) => {
 421 |           const startTime = new Date(event.start_time).toLocaleString();
 422 |           const endTime = new Date(event.end_time).toLocaleString();
 423 |           const hasBot = event.bot_param ? '🤖 Bot scheduled' : '';
 424 |           const meetingLink = event.meeting_url ? `\n   Link: ${event.meeting_url}` : '';
 425 |           const attendees =
 426 |             event.attendees && event.attendees.length > 0
 427 |               ? `\n   Attendees: ${event.attendees.map((a: { name?: string; email: string }) => a.name || a.email).join(', ')}`
 428 |               : '';
 429 | 
 430 |           return `- ${event.name}\n   From: ${startTime}\n   To: ${endTime}${meetingLink}${attendees}\n   ${hasBot} [ID: ${event.uuid}]`;
 431 |         })
 432 |         .join('\n\n');
 433 | 
 434 |       let result = `Events (${response.data.length}):\n\n${eventList}`;
 435 | 
 436 |       if (response.next) {
 437 |         result += `\n\nMore events available. Use cursor: "${response.next}" to see more.`;
 438 |       }
 439 | 
 440 |       return result;
 441 |     } catch (error) {
 442 |       return `Error listing events: ${error instanceof Error ? error.message : String(error)}`;
 443 |     }
 444 |   },
 445 | };
 446 | 
 447 | /**
 448 |  * Get event details
 449 |  */
 450 | export const getEventTool: Tool<typeof eventIdParams> = {
 451 |   name: 'getEvent',
 452 |   description: 'Get detailed information about a specific calendar event',
 453 |   parameters: eventIdParams,
 454 |   execute: async (args, context) => {
 455 |     const { session, log } = context;
 456 |     log.info('Getting event details', { eventId: args.eventId });
 457 | 
 458 |     try {
 459 |       const event = await apiRequest(session, 'get', `/calendar_events/${args.eventId}`);
 460 | 
 461 |       const startTime = new Date(event.start_time).toLocaleString();
 462 |       const endTime = new Date(event.end_time).toLocaleString();
 463 | 
 464 |       const attendees =
 465 |         event.attendees && event.attendees.length > 0
 466 |           ? event.attendees
 467 |               .map(
 468 |                 (a: { name?: string; email: string }) => `   - ${a.name || 'Unnamed'} (${a.email})`,
 469 |               )
 470 |               .join('\n')
 471 |           : '   None';
 472 | 
 473 |       let botDetails = 'None';
 474 |       if (event.bot_param) {
 475 |         botDetails = `
 476 |    Name: ${event.bot_param.bot_name}
 477 |    Recording Mode: ${event.bot_param.recording_mode || 'speaker_view'}
 478 |    Meeting Type: ${event.bot_param.extra?.meetingType || 'Not specified'}`;
 479 |       }
 480 | 
 481 |       return `Event Details:
 482 | Title: ${event.name}
 483 | Time: ${startTime} to ${endTime}
 484 | Meeting URL: ${event.meeting_url || 'Not available'}
 485 | Is Organizer: ${event.is_organizer ? 'Yes' : 'No'}
 486 | Is Recurring: ${event.is_recurring ? 'Yes' : 'No'}
 487 | ${event.recurring_event_id ? `Recurring Event ID: ${event.recurring_event_id}` : ''}
 488 | 
 489 | Attendees:
 490 | ${attendees}
 491 | 
 492 | Bot Configuration:
 493 | ${botDetails}
 494 | 
 495 | Event ID: ${event.uuid}`;
 496 |     } catch (error) {
 497 |       return `Error getting event details: ${error instanceof Error ? error.message : String(error)}`;
 498 |     }
 499 |   },
 500 | };
 501 | 
 502 | /**
 503 |  * Schedule a recording bot
 504 |  */
 505 | export const scheduleRecordingTool: Tool<typeof scheduleRecordingParams> = {
 506 |   name: 'scheduleRecording',
 507 |   description: 'Schedule a bot to record an upcoming meeting from your calendar',
 508 |   parameters: scheduleRecordingParams,
 509 |   execute: async (args, context) => {
 510 |     const { session, log } = context;
 511 |     log.info('Scheduling meeting recording', {
 512 |       eventId: args.eventId,
 513 |       botName: args.botName,
 514 |       recordingMode: args.recordingMode,
 515 |       allOccurrences: args.allOccurrences,
 516 |     });
 517 | 
 518 |     const payload: any = {
 519 |       bot_name: args.botName,
 520 |       extra: args.extra || {},
 521 |     };
 522 | 
 523 |     if (args.botImage) payload.bot_image = args.botImage;
 524 |     if (args.entryMessage) payload.enter_message = args.entryMessage;
 525 |     if (args.recordingMode) payload.recording_mode = args.recordingMode;
 526 | 
 527 |     if (args.speechToTextProvider) {
 528 |       payload.speech_to_text = {
 529 |         provider: args.speechToTextProvider,
 530 |       };
 531 | 
 532 |       if (args.speechToTextApiKey) {
 533 |         payload.speech_to_text.api_key = args.speechToTextApiKey;
 534 |       }
 535 |     }
 536 | 
 537 |     try {
 538 |       let url = `/calendar_events/${args.eventId}/bot`;
 539 |       if (args.allOccurrences) {
 540 |         url += `?all_occurrences=true`;
 541 |       }
 542 | 
 543 |       const response = await apiRequest(session, 'post', url, payload);
 544 | 
 545 |       // Check if we got a successful response with event data
 546 |       if (Array.isArray(response) && response.length > 0) {
 547 |         const eventCount = response.length;
 548 |         const firstEventName = response[0].name;
 549 | 
 550 |         if (eventCount === 1) {
 551 |           return `Recording has been scheduled successfully for "${firstEventName}".`;
 552 |         } else {
 553 |           return `Recording has been scheduled successfully for ${eventCount} instances of "${firstEventName}".`;
 554 |         }
 555 |       }
 556 | 
 557 |       return 'Recording has been scheduled successfully.';
 558 |     } catch (error) {
 559 |       return `Error scheduling recording: ${error instanceof Error ? error.message : String(error)}`;
 560 |     }
 561 |   },
 562 | };
 563 | 
 564 | /**
 565 |  * Cancel a scheduled recording
 566 |  */
 567 | export const cancelRecordingTool: Tool<typeof cancelRecordingParams> = {
 568 |   name: 'cancelRecording',
 569 |   description: 'Cancel a previously scheduled recording',
 570 |   parameters: cancelRecordingParams,
 571 |   execute: async (args, context) => {
 572 |     const { session, log } = context;
 573 |     log.info('Canceling recording', {
 574 |       eventId: args.eventId,
 575 |       allOccurrences: args.allOccurrences,
 576 |     });
 577 | 
 578 |     try {
 579 |       let url = `/calendar_events/${args.eventId}/bot`;
 580 |       if (args.allOccurrences) {
 581 |         url += `?all_occurrences=true`;
 582 |       }
 583 | 
 584 |       const response = await apiRequest(session, 'delete', url);
 585 | 
 586 |       // Check if we got a successful response with event data
 587 |       if (Array.isArray(response) && response.length > 0) {
 588 |         const eventCount = response.length;
 589 |         const firstEventName = response[0].name;
 590 | 
 591 |         if (eventCount === 1) {
 592 |           return `Recording has been canceled successfully for "${firstEventName}".`;
 593 |         } else {
 594 |           return `Recording has been canceled successfully for ${eventCount} instances of "${firstEventName}".`;
 595 |         }
 596 |       }
 597 | 
 598 |       return 'Recording has been canceled successfully.';
 599 |     } catch (error) {
 600 |       return `Error canceling recording: ${error instanceof Error ? error.message : String(error)}`;
 601 |     }
 602 |   },
 603 | };
 604 | 
 605 | /**
 606 |  * Provides guidance on setting up OAuth for calendar integration
 607 |  */
 608 | export const oauthGuidanceTool: Tool<typeof emptyParams> = {
 609 |   name: 'oauthGuidance',
 610 |   description: 'Get detailed guidance on setting up OAuth for calendar integration',
 611 |   parameters: emptyParams,
 612 |   execute: async (_args, context) => {
 613 |     const { log } = context;
 614 |     log.info('Providing OAuth guidance');
 615 | 
 616 |     return `# Calendar Integration Options
 617 | 
 618 | ## Quick Integration Options
 619 | 
 620 | You have two simple ways to integrate your calendar:
 621 | 
 622 | ### Option 1: Provide credentials directly in this chat
 623 | You can simply provide your credentials right here:
 624 | 
 625 | \`\`\`
 626 | "Set up my calendar with these credentials:
 627 | - Platform: Google (or Microsoft)
 628 | - Client ID: your-client-id-here
 629 | - Client Secret: your-client-secret-here
 630 | - Refresh Token: your-refresh-token-here
 631 | - Raw Calendar ID: [email protected] (optional)"
 632 | \`\`\`
 633 | 
 634 | ### Option 2: Configure once in Claude Desktop settings (recommended)
 635 | For a more permanent solution that doesn't require entering credentials each time:
 636 | 
 637 | 1. Edit your configuration file:
 638 |    \`\`\`bash
 639 |    vim ~/Library/Application\\ Support/Claude/claude_desktop_config.json
 640 |    \`\`\`
 641 | 
 642 | 2. Add the \`calendarOAuth\` section to your botConfig:
 643 |    \`\`\`json
 644 |    "botConfig": {
 645 |      // other bot configuration...
 646 |      
 647 |      "calendarOAuth": {
 648 |        "platform": "Google",  // or "Microsoft"
 649 |        "clientId": "YOUR_OAUTH_CLIENT_ID",
 650 |        "clientSecret": "YOUR_OAUTH_CLIENT_SECRET", 
 651 |        "refreshToken": "YOUR_REFRESH_TOKEN",
 652 |        "rawCalendarId": "[email protected]"  // Optional
 653 |      }
 654 |    }
 655 |    \`\`\`
 656 | 
 657 | 3. Save the file and restart Claude Desktop
 658 | 
 659 | > **Note:** Calendar integration is completely optional. You can use Meeting BaaS without connecting a calendar.
 660 | 
 661 | ## Need OAuth Credentials?
 662 | 
 663 | If you need to obtain OAuth credentials first, here's how:
 664 | 
 665 | <details>
 666 | <summary>## Detailed Google Calendar OAuth Setup Instructions</summary>
 667 | 
 668 | ### Step 1: Create a Google Cloud Project
 669 | 1. Go to [Google Cloud Console](https://console.cloud.google.com)
 670 | 2. Create a new project or select an existing one
 671 | 3. Enable the Google Calendar API for your project
 672 | 
 673 | ### Step 2: Set Up OAuth Consent Screen
 674 | 1. Go to "OAuth consent screen" in the left sidebar
 675 | 2. Select user type (Internal or External)
 676 | 3. Fill in required app information
 677 | 4. Add scopes for Calendar API:
 678 |    - \`https://www.googleapis.com/auth/calendar.readonly\`
 679 |    - \`https://www.googleapis.com/auth/calendar.events.readonly\`
 680 | 
 681 | ### Step 3: Create OAuth Client ID
 682 | 1. Go to "Credentials" in the left sidebar
 683 | 2. Click "Create Credentials" > "OAuth client ID"
 684 | 3. Select "Web application" as application type
 685 | 4. Add authorized redirect URIs (including \`https://developers.google.com/oauthplayground\` for testing)
 686 | 5. Save your Client ID and Client Secret
 687 | 
 688 | ### Step 4: Get Refresh Token
 689 | 1. Go to [OAuth Playground](https://developers.google.com/oauthplayground)
 690 | 2. Click the gear icon (settings) and check "Use your own OAuth credentials"
 691 | 3. Enter your Client ID and Client Secret
 692 | 4. Select Calendar API scopes and authorize
 693 | 5. Exchange authorization code for tokens
 694 | 6. Save the refresh token
 695 | </details>
 696 | 
 697 | <details>
 698 | <summary>## Detailed Microsoft Calendar OAuth Setup Instructions</summary>
 699 | 
 700 | ### Step 1: Register Application in Azure
 701 | 1. Go to [Azure Portal](https://portal.azure.com)
 702 | 2. Navigate to "App registrations" and create a new registration
 703 | 3. Set redirect URIs (web or mobile as appropriate)
 704 | 
 705 | ### Step 2: Set API Permissions
 706 | 1. Go to "API permissions" in your app registration
 707 | 2. Add Microsoft Graph permissions:
 708 |    - \`Calendars.Read\`
 709 |    - \`User.Read\`
 710 | 3. Grant admin consent if required
 711 | 
 712 | ### Step 3: Create Client Secret
 713 | 1. Go to "Certificates & secrets"
 714 | 2. Create a new client secret and save the value immediately
 715 | 
 716 | ### Step 4: Get Refresh Token
 717 | 1. Use Microsoft's OAuth endpoints to get an authorization code
 718 | 2. Exchange the code for an access token and refresh token
 719 | 3. Save the refresh token
 720 | </details>
 721 | 
 722 | ## Using the Integration Tools
 723 | 
 724 | Once you have your credentials, you can:
 725 | 
 726 | 1. Use \`listRawCalendars\` to see available calendars
 727 | 2. Use \`setupCalendarOAuth\` to integrate a specific calendar
 728 | 3. Use \`listCalendars\` to verify the integration
 729 | 
 730 | Need help with a specific step? Just ask!`;
 731 |   },
 732 | };
 733 | 
 734 | /**
 735 |  * Helper function to check if a calendar has the Meeting BaaS integration
 736 |  */
 737 | export const checkCalendarIntegrationTool: Tool<typeof emptyParams> = {
 738 |   name: 'checkCalendarIntegration',
 739 |   description: 'Check and diagnose calendar integration status',
 740 |   parameters: emptyParams,
 741 |   execute: async (_args, context) => {
 742 |     const { session, log } = context;
 743 |     log.info('Checking calendar integration status');
 744 | 
 745 |     try {
 746 |       // List calendars
 747 |       const calendars = await apiRequest(session, 'get', '/calendars/');
 748 | 
 749 |       if (!calendars || calendars.length === 0) {
 750 |         return `No calendars integrated. To integrate a calendar:
 751 | 
 752 | 1. You need Google/Microsoft OAuth credentials:
 753 |    - Client ID
 754 |    - Client Secret
 755 |    - Refresh Token
 756 | 
 757 | 2. Use the \`oauthGuidance\` tool for detailed steps to obtain these credentials.
 758 | 
 759 | 3. Use the \`setupCalendarOAuth\` tool to connect your calendar.
 760 | 
 761 | Example command:
 762 | "Connect my Google Calendar using these OAuth credentials: [client-id], [client-secret], [refresh-token]"`;
 763 |       }
 764 | 
 765 |       // List some recent events to check functionality
 766 |       const calendarId = calendars[0].uuid;
 767 |       const events = await apiRequest(
 768 |         session,
 769 |         'get',
 770 |         `/calendar_events/?calendar_id=${calendarId}&status=upcoming`,
 771 |       );
 772 | 
 773 |       let eventStatus = '';
 774 |       if (!events.data || events.data.length === 0) {
 775 |         eventStatus = 'No upcoming events found in this calendar.';
 776 |       } else {
 777 |         const eventCount = events.data.length;
 778 |         const scheduledCount = events.data.filter((e: any) => e.bot_param).length;
 779 |         eventStatus = `Found ${eventCount} upcoming events, ${scheduledCount} have recording bots scheduled.`;
 780 |       }
 781 | 
 782 |       return `Calendar integration status: ACTIVE
 783 | 
 784 | Found ${calendars.length} integrated calendar(s):
 785 | ${calendars.map((cal: any) => `- ${cal.name} (${cal.email}) [ID: ${cal.uuid}]`).join('\n')}
 786 | 
 787 | ${eventStatus}
 788 | 
 789 | To schedule recordings for upcoming meetings:
 790 | 1. Use \`listUpcomingMeetings\` to see available meetings
 791 | 2. Use \`scheduleRecording\` to set up a recording bot for a meeting
 792 | 
 793 | To manage calendar integrations:
 794 | - Use \`resyncAllCalendars\` to force a refresh of calendar data
 795 | - Use \`deleteCalendar\` to remove a calendar integration`;
 796 |     } catch (error) {
 797 |       return `Error checking calendar integration: ${error instanceof Error ? error.message : String(error)}
 798 | 
 799 | This could indicate:
 800 | - API authentication issues
 801 | - Missing or expired OAuth credentials
 802 | - Network connectivity problems
 803 | 
 804 | Try the following:
 805 | 1. Verify your API key is correct
 806 | 2. Check if OAuth credentials need to be refreshed
 807 | 3. Use \`oauthGuidance\` for help setting up OAuth correctly`;
 808 |     }
 809 |   },
 810 | };
 811 | 
 812 | /**
 813 |  * List events with comprehensive filtering - VERSION WITH DYNAMIC CREDENTIALS
 814 |  */
 815 | const listEventsWithCredentialsParams = z.object({
 816 |   calendarId: z.string().describe('UUID of the calendar to retrieve events from'),
 817 |   apiKey: z.string().describe('API key for authentication'),
 818 |   status: z
 819 |     .enum(['upcoming', 'past', 'all'])
 820 |     .optional()
 821 |     .describe('Filter events by status (upcoming, past, all)'),
 822 |   startDateGte: z
 823 |     .string()
 824 |     .optional()
 825 |     .describe('Filter events with start date greater than or equal to (ISO format)'),
 826 |   startDateLte: z
 827 |     .string()
 828 |     .optional()
 829 |     .describe('Filter events with start date less than or equal to (ISO format)'),
 830 |   attendeeEmail: z.string().optional().describe('Filter events with specific attendee email'),
 831 |   organizerEmail: z.string().optional().describe('Filter events with specific organizer email'),
 832 |   updatedAtGte: z
 833 |     .string()
 834 |     .optional()
 835 |     .describe('Filter events updated after specified date (ISO format)'),
 836 |   cursor: z.string().optional().describe('Pagination cursor for retrieving more results'),
 837 |   limit: z.number().optional().describe('Maximum number of events to return'),
 838 | });
 839 | 
 840 | export const listEventsWithCredentialsTool: Tool<typeof listEventsWithCredentialsParams> = {
 841 |   name: 'listEventsWithCredentials',
 842 |   description:
 843 |     'List calendar events with comprehensive filtering options using directly provided credentials',
 844 |   parameters: listEventsWithCredentialsParams,
 845 |   execute: async (args, context) => {
 846 |     const { log } = context;
 847 | 
 848 |     // Create a session with the provided API key
 849 |     const session = { apiKey: args.apiKey };
 850 | 
 851 |     log.info('Listing calendar events with provided credentials', {
 852 |       calendarId: args.calendarId,
 853 |       filters: args,
 854 |     });
 855 | 
 856 |     // Build the query parameters
 857 |     let queryParams = `calendar_id=${args.calendarId}`;
 858 |     if (args.status) queryParams += `&status=${args.status}`;
 859 |     if (args.startDateGte)
 860 |       queryParams += `&start_date_gte=${encodeURIComponent(args.startDateGte)}`;
 861 |     if (args.startDateLte)
 862 |       queryParams += `&start_date_lte=${encodeURIComponent(args.startDateLte)}`;
 863 |     if (args.attendeeEmail)
 864 |       queryParams += `&attendee_email=${encodeURIComponent(args.attendeeEmail)}`;
 865 |     if (args.organizerEmail)
 866 |       queryParams += `&organizer_email=${encodeURIComponent(args.organizerEmail)}`;
 867 |     if (args.updatedAtGte)
 868 |       queryParams += `&updated_at_gte=${encodeURIComponent(args.updatedAtGte)}`;
 869 |     if (args.cursor) queryParams += `&cursor=${encodeURIComponent(args.cursor)}`;
 870 |     if (args.limit) queryParams += `&limit=${args.limit}`;
 871 | 
 872 |     try {
 873 |       const response = await apiRequest(session, 'get', `/calendar_events/?${queryParams}`);
 874 | 
 875 |       if (!response.data || response.data.length === 0) {
 876 |         return 'No events found matching your criteria.';
 877 |       }
 878 | 
 879 |       const eventList = response.data
 880 |         .map((event: CalendarEvent) => {
 881 |           const startTime = new Date(event.start_time).toLocaleString();
 882 |           const endTime = new Date(event.end_time).toLocaleString();
 883 |           const hasBot =
 884 |             event.bot_param && typeof event.bot_param === 'object' && 'uuid' in event.bot_param;
 885 |           const meetingStatus = hasBot ? '🟢 Recording scheduled' : '⚪ No recording';
 886 | 
 887 |           // Get attendee names
 888 |           const attendeeList =
 889 |             (event.attendees || []).map((a) => a.name || a.email).join(', ') || 'None listed';
 890 | 
 891 |           return (
 892 |             `## ${event.name}\n` +
 893 |             `**Time**: ${startTime} to ${endTime}\n` +
 894 |             `**Status**: ${meetingStatus}\n` +
 895 |             `**Event ID**: ${event.uuid}\n` +
 896 |             `**Organizer**: ${event.is_organizer ? 'You' : 'Other'}\n` +
 897 |             `**Attendees**: ${attendeeList}\n`
 898 |           );
 899 |         })
 900 |         .join('\n\n');
 901 | 
 902 |       let result = `Calendar Events:\n\n${eventList}`;
 903 | 
 904 |       if (response.next) {
 905 |         result += `\n\nMore events available. Use 'cursor: ${response.next}' to see more.`;
 906 |       }
 907 | 
 908 |       return result;
 909 |     } catch (error) {
 910 |       return `Error listing events: ${error instanceof Error ? error.message : String(error)}`;
 911 |     }
 912 |   },
 913 | };
 914 | 
 915 | /**
 916 |  * Schedule a recording with direct credentials
 917 |  */
 918 | const scheduleRecordingWithCredentialsParams = z.object({
 919 |   eventId: z.string().uuid().describe('UUID of the calendar event to record'),
 920 |   apiKey: z.string().describe('API key for authentication'),
 921 |   botName: z.string().describe('Name to display for the bot in the meeting'),
 922 |   botImage: z.string().url().optional().describe("URL to an image for the bot's avatar (optional)"),
 923 |   entryMessage: z
 924 |     .string()
 925 |     .optional()
 926 |     .describe('Message the bot will send when joining the meeting (optional)'),
 927 |   recordingMode: z
 928 |     .enum(['speaker_view', 'gallery_view', 'audio_only'] as const)
 929 |     .default('speaker_view')
 930 |     .describe("Recording mode: 'speaker_view' (default), 'gallery_view', or 'audio_only'"),
 931 |   speechToTextProvider: z
 932 |     .enum(['Gladia', 'Runpod', 'Default'] as const)
 933 |     .optional()
 934 |     .describe('Provider for speech-to-text transcription (optional)'),
 935 |   speechToTextApiKey: z
 936 |     .string()
 937 |     .optional()
 938 |     .describe('API key for the speech-to-text provider if required (optional)'),
 939 |   extra: z
 940 |     .record(z.any())
 941 |     .optional()
 942 |     .describe('Additional metadata about the meeting (e.g., meetingType, participants)'),
 943 |   allOccurrences: z
 944 |     .boolean()
 945 |     .optional()
 946 |     .default(false)
 947 |     .describe(
 948 |       'For recurring events, schedule recording for all occurrences (true) or just this instance (false)',
 949 |     ),
 950 | });
 951 | 
 952 | export const scheduleRecordingWithCredentialsTool: Tool<
 953 |   typeof scheduleRecordingWithCredentialsParams
 954 | > = {
 955 |   name: 'scheduleRecordingWithCredentials',
 956 |   description: 'Schedule a bot to record an upcoming meeting using directly provided credentials',
 957 |   parameters: scheduleRecordingWithCredentialsParams,
 958 |   execute: async (args, context) => {
 959 |     const { log } = context;
 960 | 
 961 |     // Create a session with the provided API key
 962 |     const session = { apiKey: args.apiKey };
 963 | 
 964 |     log.info('Scheduling meeting recording with provided credentials', {
 965 |       eventId: args.eventId,
 966 |       botName: args.botName,
 967 |       recordingMode: args.recordingMode,
 968 |       allOccurrences: args.allOccurrences,
 969 |     });
 970 | 
 971 |     const payload: any = {
 972 |       bot_name: args.botName,
 973 |       extra: args.extra || {},
 974 |     };
 975 | 
 976 |     if (args.botImage) payload.bot_image = args.botImage;
 977 |     if (args.entryMessage) payload.enter_message = args.entryMessage;
 978 |     if (args.recordingMode) payload.recording_mode = args.recordingMode;
 979 | 
 980 |     if (args.speechToTextProvider) {
 981 |       payload.speech_to_text = {
 982 |         provider: args.speechToTextProvider,
 983 |       };
 984 | 
 985 |       if (args.speechToTextApiKey) {
 986 |         payload.speech_to_text.api_key = args.speechToTextApiKey;
 987 |       }
 988 |     }
 989 | 
 990 |     try {
 991 |       let url = `/calendar_events/${args.eventId}/bot`;
 992 |       if (args.allOccurrences) {
 993 |         url += `?all_occurrences=true`;
 994 |       }
 995 | 
 996 |       const response = await apiRequest(session, 'post', url, payload);
 997 | 
 998 |       // Check if we got a successful response with event data
 999 |       if (Array.isArray(response) && response.length > 0) {
1000 |         const eventCount = response.length;
1001 |         const firstEventName = response[0].name;
1002 | 
1003 |         if (eventCount === 1) {
1004 |           return `Recording has been scheduled successfully for "${firstEventName}".`;
1005 |         } else {
1006 |           return `Recording has been scheduled successfully for ${eventCount} instances of "${firstEventName}".`;
1007 |         }
1008 |       }
1009 | 
1010 |       return 'Recording has been scheduled successfully.';
1011 |     } catch (error) {
1012 |       return `Error scheduling recording: ${error instanceof Error ? error.message : String(error)}`;
1013 |     }
1014 |   },
1015 | };
1016 | 
1017 | /**
1018 |  * Cancel a scheduled recording with direct credentials
1019 |  */
1020 | const cancelRecordingWithCredentialsParams = z.object({
1021 |   eventId: z.string().uuid().describe('UUID of the calendar event to cancel recording for'),
1022 |   apiKey: z.string().describe('API key for authentication'),
1023 |   allOccurrences: z
1024 |     .boolean()
1025 |     .optional()
1026 |     .default(false)
1027 |     .describe(
1028 |       'For recurring events, cancel recording for all occurrences (true) or just this instance (false)',
1029 |     ),
1030 | });
1031 | 
1032 | export const cancelRecordingWithCredentialsTool: Tool<typeof cancelRecordingWithCredentialsParams> =
1033 |   {
1034 |     name: 'cancelRecordingWithCredentials',
1035 |     description: 'Cancel a previously scheduled recording using directly provided credentials',
1036 |     parameters: cancelRecordingWithCredentialsParams,
1037 |     execute: async (args, context) => {
1038 |       const { log } = context;
1039 | 
1040 |       // Create a session with the provided API key
1041 |       const session = { apiKey: args.apiKey };
1042 | 
1043 |       log.info('Canceling recording with provided credentials', {
1044 |         eventId: args.eventId,
1045 |         allOccurrences: args.allOccurrences,
1046 |       });
1047 | 
1048 |       try {
1049 |         let url = `/calendar_events/${args.eventId}/bot`;
1050 |         if (args.allOccurrences) {
1051 |           url += `?all_occurrences=true`;
1052 |         }
1053 | 
1054 |         await apiRequest(session, 'delete', url);
1055 | 
1056 |         return `Recording has been successfully canceled for event ${args.eventId}${args.allOccurrences ? ' and all its occurrences' : ''}.`;
1057 |       } catch (error) {
1058 |         return `Error canceling recording: ${error instanceof Error ? error.message : String(error)}`;
1059 |       }
1060 |     },
1061 |   };
1062 | 
```

--------------------------------------------------------------------------------
/openapi.json:
--------------------------------------------------------------------------------

```json
   1 | {
   2 |   "openapi": "3.1.0",
   3 |   "info": {
   4 |     "title": "Meeting BaaS API",
   5 |     "summary": "API for recording and transcribing video meetings across Zoom, Google Meet, and Microsoft Teams. Features include bot management, calendar integration, and transcription services.",
   6 |     "description": "Meeting BaaS API",
   7 |     "termsOfService": "https://meetingbaas.com/terms-and-conditions",
   8 |     "version": "1.1"
   9 |   },
  10 |   "servers": [{ "url": "https://api.meetingbaas.com", "description": "Production server" }],
  11 |   "paths": {
  12 |     "/bots/": {
  13 |       "post": {
  14 |         "summary": "Join",
  15 |         "description": "Have a bot join a meeting, now or in the future",
  16 |         "operationId": "join",
  17 |         "requestBody": {
  18 |           "content": {
  19 |             "application/json": { "schema": { "$ref": "#/components/schemas/JoinRequest" } }
  20 |           },
  21 |           "required": true
  22 |         },
  23 |         "responses": {
  24 |           "200": {
  25 |             "description": "",
  26 |             "content": {
  27 |               "application/json": { "schema": { "$ref": "#/components/schemas/JoinResponse" } }
  28 |             }
  29 |           }
  30 |         },
  31 |         "security": [{ "ApiKeyAuth": [] }, { "LegacyApiKeyAuth": [] }]
  32 |       }
  33 |     },
  34 |     "/bots/{uuid}": {
  35 |       "delete": {
  36 |         "summary": "Leave",
  37 |         "description": "Leave",
  38 |         "operationId": "leave",
  39 |         "responses": {
  40 |           "200": {
  41 |             "description": "",
  42 |             "content": {
  43 |               "application/json": { "schema": { "$ref": "#/components/schemas/LeaveResponse" } }
  44 |             }
  45 |           }
  46 |         }
  47 |       }
  48 |     },
  49 |     "/bots/meeting_data": {
  50 |       "get": {
  51 |         "summary": "Get Meeting Data",
  52 |         "description": "Get meeting recording and metadata",
  53 |         "operationId": "get_meeting_data",
  54 |         "parameters": [
  55 |           {
  56 |             "in": "query",
  57 |             "name": "bot_id",
  58 |             "required": true,
  59 |             "schema": { "type": "string" },
  60 |             "style": "form"
  61 |           }
  62 |         ],
  63 |         "responses": {
  64 |           "200": {
  65 |             "description": "",
  66 |             "content": {
  67 |               "application/json": { "schema": { "$ref": "#/components/schemas/Metadata" } }
  68 |             }
  69 |           }
  70 |         }
  71 |       }
  72 |     },
  73 |     "/bots/{uuid}/delete_data": {
  74 |       "post": {
  75 |         "summary": "Delete Data",
  76 |         "description": "Deletes a bot's data including recording, transcription, and logs. Only metadata is retained. Rate limited to 5 requests per minute per API key.",
  77 |         "operationId": "delete_data",
  78 |         "responses": {
  79 |           "200": {
  80 |             "description": "",
  81 |             "content": {
  82 |               "application/json": { "schema": { "$ref": "#/components/schemas/DeleteResponse" } }
  83 |             }
  84 |           },
  85 |           "401": { "description": "no content" },
  86 |           "403": { "description": "no content" },
  87 |           "404": { "description": "no content" },
  88 |           "429": { "description": "no content" }
  89 |         }
  90 |       }
  91 |     },
  92 |     "/bots/bots_with_metadata": {
  93 |       "get": {
  94 |         "summary": "List Bots with Metadata",
  95 |         "description": "Preview endpoint. 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.",
  96 |         "operationId": "bots_with_metadata",
  97 |         "parameters": [
  98 |           {
  99 |             "in": "query",
 100 |             "name": "bot_name",
 101 |             "description": "Filter bots by name containing this string.\n\nPerforms a case-insensitive partial match on the bot's name. Useful for finding bots with specific naming conventions or to locate a particular bot when you don't have its ID.\n\nExample: \"Sales\" would match \"Sales Meeting\", \"Quarterly Sales\", etc.",
 102 |             "schema": {
 103 |               "description": "Filter bots by name containing this string.\n\nPerforms a case-insensitive partial match on the bot's name. Useful for finding bots with specific naming conventions or to locate a particular bot when you don't have its ID.\n\nExample: \"Sales\" would match \"Sales Meeting\", \"Quarterly Sales\", etc.",
 104 |               "type": ["string", "null"]
 105 |             },
 106 |             "style": "form"
 107 |           },
 108 |           {
 109 |             "in": "query",
 110 |             "name": "created_after",
 111 |             "description": "Filter bots created after this date (ISO format).\n\nLimits results to bots created at or after the specified timestamp. Used for time-based filtering to find recent additions.\n\nFormat: ISO-8601 date-time string (YYYY-MM-DDThh:mm:ss) Example: \"2023-05-01T00:00:00\"",
 112 |             "schema": {
 113 |               "description": "Filter bots created after this date (ISO format).\n\nLimits results to bots created at or after the specified timestamp. Used for time-based filtering to find recent additions.\n\nFormat: ISO-8601 date-time string (YYYY-MM-DDThh:mm:ss) Example: \"2023-05-01T00:00:00\"",
 114 |               "type": ["string", "null"]
 115 |             },
 116 |             "style": "form"
 117 |           },
 118 |           {
 119 |             "in": "query",
 120 |             "name": "created_before",
 121 |             "description": "Filter bots created before this date (ISO format).\n\nLimits results to bots created at or before the specified timestamp. Used for time-based filtering to exclude recent additions.\n\nFormat: ISO-8601 date-time string (YYYY-MM-DDThh:mm:ss) Example: \"2023-05-31T23:59:59\"",
 122 |             "schema": {
 123 |               "description": "Filter bots created before this date (ISO format).\n\nLimits results to bots created at or before the specified timestamp. Used for time-based filtering to exclude recent additions.\n\nFormat: ISO-8601 date-time string (YYYY-MM-DDThh:mm:ss) Example: \"2023-05-31T23:59:59\"",
 124 |               "type": ["string", "null"]
 125 |             },
 126 |             "style": "form"
 127 |           },
 128 |           {
 129 |             "in": "query",
 130 |             "name": "cursor",
 131 |             "description": "Cursor for pagination, obtained from previous response.\n\nUsed for retrieving the next set of results after a previous call. The cursor value is returned in the `nextCursor` field of responses that have additional results available.\n\nFormat: Base64-encoded string containing pagination metadata",
 132 |             "schema": {
 133 |               "description": "Cursor for pagination, obtained from previous response.\n\nUsed for retrieving the next set of results after a previous call. The cursor value is returned in the `nextCursor` field of responses that have additional results available.\n\nFormat: Base64-encoded string containing pagination metadata",
 134 |               "type": ["string", "null"]
 135 |             },
 136 |             "style": "form"
 137 |           },
 138 |           {
 139 |             "in": "query",
 140 |             "name": "filter_by_extra",
 141 |             "description": "Filter bots by matching values in the extra JSON payload.\n\nThis parameter performs in-memory filtering on the `extra` JSON field, similar to a SQL WHERE clause. It reduces the result set to only include bots that match all specified conditions.\n\nFormat specifications: - Single condition: \"field:value\" - Multiple conditions: \"field1:value1,field2:value2\"\n\nExamples: - \"customer_id:12345\" - Only bots with this customer ID - \"status:active,project:sales\" - Only active bots from sales projects\n\nNotes: - All conditions must match for a bot to be included - Values are matched exactly (case-sensitive) - Bots without the specified field are excluded",
 142 |             "schema": {
 143 |               "description": "Filter bots by matching values in the extra JSON payload.\n\nThis parameter performs in-memory filtering on the `extra` JSON field, similar to a SQL WHERE clause. It reduces the result set to only include bots that match all specified conditions.\n\nFormat specifications: - Single condition: \"field:value\" - Multiple conditions: \"field1:value1,field2:value2\"\n\nExamples: - \"customer_id:12345\" - Only bots with this customer ID - \"status:active,project:sales\" - Only active bots from sales projects\n\nNotes: - All conditions must match for a bot to be included - Values are matched exactly (case-sensitive) - Bots without the specified field are excluded",
 144 |               "type": ["string", "null"]
 145 |             },
 146 |             "style": "form"
 147 |           },
 148 |           {
 149 |             "in": "query",
 150 |             "name": "limit",
 151 |             "description": "Maximum number of bots to return in a single request.\n\nLimits the number of results returned in a single API call. This parameter helps control response size and page length.\n\nDefault: 10 Minimum: 1 Maximum: 50",
 152 |             "schema": {
 153 |               "description": "Maximum number of bots to return in a single request.\n\nLimits the number of results returned in a single API call. This parameter helps control response size and page length.\n\nDefault: 10 Minimum: 1 Maximum: 50",
 154 |               "default": 10,
 155 |               "type": "integer",
 156 |               "format": "int32"
 157 |             },
 158 |             "style": "form"
 159 |           },
 160 |           {
 161 |             "in": "query",
 162 |             "name": "meeting_url",
 163 |             "description": "Filter bots by meeting URL containing this string.\n\nPerforms a case-insensitive partial match on the bot's meeting URL. Use this to find bots associated with specific meeting platforms or particular meeting IDs.\n\nExample: \"zoom.us\" would match all Zoom meetings",
 164 |             "schema": {
 165 |               "description": "Filter bots by meeting URL containing this string.\n\nPerforms a case-insensitive partial match on the bot's meeting URL. Use this to find bots associated with specific meeting platforms or particular meeting IDs.\n\nExample: \"zoom.us\" would match all Zoom meetings",
 166 |               "type": ["string", "null"]
 167 |             },
 168 |             "style": "form"
 169 |           },
 170 |           {
 171 |             "in": "query",
 172 |             "name": "sort_by_extra",
 173 |             "description": "Sort the results by a field in the extra JSON payload.\n\nThis parameter performs in-memory sorting on the `extra` JSON field, similar to a SQL ORDER BY clause. It changes the order of results but not which results are included.\n\nFormat specifications: - Default (ascending): \"field\" - Explicit direction: \"field:asc\" or \"field:desc\"\n\nExamples: - \"customer_id\" - Sort by customer_id (ascending) - \"priority:desc\" - Sort by priority (descending)\n\nNotes: - Applied after all filtering - String comparison is used for sorting - Bots with the field come before bots without it - Can be combined with filter_by_extra",
 174 |             "schema": {
 175 |               "description": "Sort the results by a field in the extra JSON payload.\n\nThis parameter performs in-memory sorting on the `extra` JSON field, similar to a SQL ORDER BY clause. It changes the order of results but not which results are included.\n\nFormat specifications: - Default (ascending): \"field\" - Explicit direction: \"field:asc\" or \"field:desc\"\n\nExamples: - \"customer_id\" - Sort by customer_id (ascending) - \"priority:desc\" - Sort by priority (descending)\n\nNotes: - Applied after all filtering - String comparison is used for sorting - Bots with the field come before bots without it - Can be combined with filter_by_extra",
 176 |               "type": ["string", "null"]
 177 |             },
 178 |             "style": "form"
 179 |           },
 180 |           {
 181 |             "in": "query",
 182 |             "name": "speaker_name",
 183 |             "description": "NOTE: this is a preview feature and not yet available\n\nFilter bots by speaker name containing this string.\n\nPerforms a case-insensitive partial match on the speakers in the meeting. Useful for finding meetings that included a specific person.\n\nExample: \"John\" would match meetings with speakers like \"John Smith\" or \"John Doe\"",
 184 |             "schema": {
 185 |               "description": "NOTE: this is a preview feature and not yet available\n\nFilter bots by speaker name containing this string.\n\nPerforms a case-insensitive partial match on the speakers in the meeting. Useful for finding meetings that included a specific person.\n\nExample: \"John\" would match meetings with speakers like \"John Smith\" or \"John Doe\"",
 186 |               "type": ["string", "null"]
 187 |             },
 188 |             "style": "form"
 189 |           }
 190 |         ],
 191 |         "responses": {
 192 |           "200": {
 193 |             "description": "",
 194 |             "content": {
 195 |               "application/json": {
 196 |                 "schema": { "$ref": "#/components/schemas/ListRecentBotsResponse" }
 197 |               }
 198 |             }
 199 |           }
 200 |         }
 201 |       }
 202 |     },
 203 |     "/bots/retranscribe": {
 204 |       "post": {
 205 |         "summary": "Retranscribe Bot",
 206 |         "description": "Transcribe or retranscribe a bot's audio using the Default or your provided Speech to Text Provider",
 207 |         "operationId": "retranscribe_bot",
 208 |         "requestBody": {
 209 |           "content": {
 210 |             "application/json": { "schema": { "$ref": "#/components/schemas/RetranscribeBody" } }
 211 |           },
 212 |           "required": true
 213 |         },
 214 |         "responses": {
 215 |           "200": { "description": "description" },
 216 |           "202": { "description": "no content" }
 217 |         }
 218 |       }
 219 |     },
 220 |     "/calendars/raw": {
 221 |       "post": {
 222 |         "tags": ["Calendars"],
 223 |         "summary": "List Raw Calendars",
 224 |         "description": "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.",
 225 |         "operationId": "list_raw_calendars",
 226 |         "requestBody": {
 227 |           "content": {
 228 |             "application/json": {
 229 |               "schema": { "$ref": "#/components/schemas/ListRawCalendarsParams" }
 230 |             }
 231 |           },
 232 |           "required": true
 233 |         },
 234 |         "responses": {
 235 |           "200": {
 236 |             "description": "",
 237 |             "content": {
 238 |               "application/json": {
 239 |                 "schema": { "$ref": "#/components/schemas/ListRawCalendarsResponse" }
 240 |               }
 241 |             }
 242 |           }
 243 |         }
 244 |       }
 245 |     },
 246 |     "/calendars/": {
 247 |       "get": {
 248 |         "tags": ["Calendars"],
 249 |         "summary": "List Calendars",
 250 |         "description": "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.",
 251 |         "operationId": "list_calendars",
 252 |         "responses": {
 253 |           "200": {
 254 |             "description": "",
 255 |             "content": {
 256 |               "application/json": {
 257 |                 "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Calendar" } }
 258 |               }
 259 |             }
 260 |           }
 261 |         }
 262 |       },
 263 |       "post": {
 264 |         "tags": ["Calendars"],
 265 |         "summary": "Create Calendar",
 266 |         "description": "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.",
 267 |         "operationId": "create_calendar",
 268 |         "requestBody": {
 269 |           "content": {
 270 |             "application/json": {
 271 |               "schema": { "$ref": "#/components/schemas/CreateCalendarParams" }
 272 |             }
 273 |           },
 274 |           "required": true
 275 |         },
 276 |         "responses": {
 277 |           "200": {
 278 |             "description": "",
 279 |             "content": {
 280 |               "application/json": {
 281 |                 "schema": { "$ref": "#/components/schemas/CreateCalendarResponse" }
 282 |               }
 283 |             }
 284 |           }
 285 |         }
 286 |       }
 287 |     },
 288 |     "/calendars/resync_all": {
 289 |       "post": {
 290 |         "tags": ["Calendars"],
 291 |         "summary": "Resync All Calendars",
 292 |         "description": "Initiates a complete refresh of data for all connected calendars. This operation performs a deep sync with each calendar provider (Google, Microsoft) to ensure all events are up-to-date. Each calendar is processed individually, with success or failure tracked separately. Returns a comprehensive result object containing successfully synced calendar UUIDs and detailed error messages for any failures. This endpoint is useful for ensuring the system has the latest calendar data when inconsistencies are suspected.",
 293 |         "operationId": "resync_all_calendars",
 294 |         "responses": {
 295 |           "200": {
 296 |             "description": "",
 297 |             "content": {
 298 |               "application/json": {
 299 |                 "schema": { "$ref": "#/components/schemas/ResyncAllCalendarsResponse" }
 300 |               }
 301 |             }
 302 |           }
 303 |         }
 304 |       }
 305 |     },
 306 |     "/calendars/{uuid}": {
 307 |       "get": {
 308 |         "tags": ["Calendars"],
 309 |         "summary": "Get Calendar",
 310 |         "description": "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.",
 311 |         "operationId": "get_calendar",
 312 |         "responses": {
 313 |           "200": {
 314 |             "description": "",
 315 |             "content": {
 316 |               "application/json": { "schema": { "$ref": "#/components/schemas/Calendar" } }
 317 |             }
 318 |           }
 319 |         }
 320 |       },
 321 |       "delete": {
 322 |         "tags": ["Calendars"],
 323 |         "summary": "Delete Calendar",
 324 |         "description": "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.",
 325 |         "operationId": "delete_calendar",
 326 |         "responses": { "200": { "description": "no content" } }
 327 |       },
 328 |       "patch": {
 329 |         "tags": ["Calendars"],
 330 |         "summary": "Update Calendar",
 331 |         "description": "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.",
 332 |         "operationId": "update_calendar",
 333 |         "requestBody": {
 334 |           "content": {
 335 |             "application/json": {
 336 |               "schema": { "$ref": "#/components/schemas/UpdateCalendarParams" }
 337 |             }
 338 |           },
 339 |           "required": true
 340 |         },
 341 |         "responses": {
 342 |           "200": {
 343 |             "description": "",
 344 |             "content": {
 345 |               "application/json": {
 346 |                 "schema": { "$ref": "#/components/schemas/CreateCalendarResponse" }
 347 |               }
 348 |             }
 349 |           }
 350 |         }
 351 |       }
 352 |     },
 353 |     "/calendar_events/{uuid}": {
 354 |       "get": {
 355 |         "tags": ["Calendars"],
 356 |         "summary": "Get Event",
 357 |         "description": "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.",
 358 |         "operationId": "get_event",
 359 |         "responses": {
 360 |           "200": {
 361 |             "description": "",
 362 |             "content": {
 363 |               "application/json": { "schema": { "$ref": "#/components/schemas/Event" } }
 364 |             }
 365 |           }
 366 |         }
 367 |       }
 368 |     },
 369 |     "/calendar_events/{uuid}/bot": {
 370 |       "post": {
 371 |         "tags": ["Calendars"],
 372 |         "summary": "Schedule Record Event",
 373 |         "description": "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.",
 374 |         "operationId": "schedule_record_event",
 375 |         "parameters": [
 376 |           {
 377 |             "in": "query",
 378 |             "name": "all_occurrences",
 379 |             "description": "schedule a bot to all occurences of a recurring event",
 380 |             "schema": {
 381 |               "description": "schedule a bot to all occurences of a recurring event",
 382 |               "type": ["boolean", "null"]
 383 |             },
 384 |             "style": "form"
 385 |           }
 386 |         ],
 387 |         "requestBody": {
 388 |           "content": {
 389 |             "application/json": { "schema": { "$ref": "#/components/schemas/BotParam2" } }
 390 |           },
 391 |           "required": true
 392 |         },
 393 |         "responses": {
 394 |           "200": {
 395 |             "description": "",
 396 |             "content": {
 397 |               "application/json": {
 398 |                 "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Event" } }
 399 |               }
 400 |             }
 401 |           }
 402 |         }
 403 |       },
 404 |       "delete": {
 405 |         "tags": ["Calendars"],
 406 |         "summary": "Unschedule Record Event",
 407 |         "description": "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.",
 408 |         "operationId": "unschedule_record_event",
 409 |         "parameters": [
 410 |           {
 411 |             "in": "query",
 412 |             "name": "all_occurrences",
 413 |             "description": "unschedule a bot from all occurences of a recurring event",
 414 |             "schema": {
 415 |               "description": "unschedule a bot from all occurences of a recurring event",
 416 |               "type": ["boolean", "null"]
 417 |             },
 418 |             "style": "form"
 419 |           }
 420 |         ],
 421 |         "responses": {
 422 |           "200": {
 423 |             "description": "",
 424 |             "content": {
 425 |               "application/json": {
 426 |                 "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Event" } }
 427 |               }
 428 |             }
 429 |           }
 430 |         }
 431 |       },
 432 |       "patch": {
 433 |         "tags": ["Calendars"],
 434 |         "summary": "Patch Bot",
 435 |         "description": "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.",
 436 |         "operationId": "patch_bot",
 437 |         "parameters": [
 438 |           {
 439 |             "in": "query",
 440 |             "name": "all_occurrences",
 441 |             "description": "schedule a bot to all occurences of a recurring event",
 442 |             "schema": {
 443 |               "description": "schedule a bot to all occurences of a recurring event",
 444 |               "type": ["boolean", "null"]
 445 |             },
 446 |             "style": "form"
 447 |           }
 448 |         ],
 449 |         "requestBody": {
 450 |           "content": {
 451 |             "application/json": { "schema": { "$ref": "#/components/schemas/BotParam3" } }
 452 |           },
 453 |           "required": true
 454 |         },
 455 |         "responses": {
 456 |           "200": {
 457 |             "description": "",
 458 |             "content": {
 459 |               "application/json": {
 460 |                 "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Event" } }
 461 |               }
 462 |             }
 463 |           }
 464 |         }
 465 |       }
 466 |     },
 467 |     "/calendar_events/": {
 468 |       "get": {
 469 |         "tags": ["Calendars"],
 470 |         "summary": "List Events",
 471 |         "description": "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.",
 472 |         "operationId": "list_events",
 473 |         "parameters": [
 474 |           {
 475 |             "in": "query",
 476 |             "name": "attendee_email",
 477 |             "description": "If provided, filters events to include only those with this attendee's email address Example: \"[email protected]\"",
 478 |             "schema": {
 479 |               "description": "If provided, filters events to include only those with this attendee's email address Example: \"[email protected]\"",
 480 |               "type": ["string", "null"]
 481 |             },
 482 |             "style": "form"
 483 |           },
 484 |           {
 485 |             "in": "query",
 486 |             "name": "calendar_id",
 487 |             "description": "Calendar ID to filter events by This is required to specify which calendar's events to retrieve",
 488 |             "required": true,
 489 |             "schema": {
 490 |               "description": "Calendar ID to filter events by This is required to specify which calendar's events to retrieve",
 491 |               "type": "string"
 492 |             },
 493 |             "style": "form"
 494 |           },
 495 |           {
 496 |             "in": "query",
 497 |             "name": "cursor",
 498 |             "description": "Optional cursor for pagination This value is included in the `next` field of the previous response",
 499 |             "schema": {
 500 |               "description": "Optional cursor for pagination This value is included in the `next` field of the previous response",
 501 |               "type": ["string", "null"]
 502 |             },
 503 |             "style": "form"
 504 |           },
 505 |           {
 506 |             "in": "query",
 507 |             "name": "organizer_email",
 508 |             "description": "If provided, filters events to include only those with this organizer's email address Example: \"[email protected]\"",
 509 |             "schema": {
 510 |               "description": "If provided, filters events to include only those with this organizer's email address Example: \"[email protected]\"",
 511 |               "type": ["string", "null"]
 512 |             },
 513 |             "style": "form"
 514 |           },
 515 |           {
 516 |             "in": "query",
 517 |             "name": "start_date_gte",
 518 |             "description": "If provided, filters events to include only those with a start date greater than or equal to this timestamp Format: ISO-8601 string, e.g., \"2023-01-01T00:00:00Z\"",
 519 |             "schema": {
 520 |               "description": "If provided, filters events to include only those with a start date greater than or equal to this timestamp Format: ISO-8601 string, e.g., \"2023-01-01T00:00:00Z\"",
 521 |               "type": ["string", "null"]
 522 |             },
 523 |             "style": "form"
 524 |           },
 525 |           {
 526 |             "in": "query",
 527 |             "name": "start_date_lte",
 528 |             "description": "If provided, filters events to include only those with a start date less than or equal to this timestamp Format: ISO-8601 string, e.g., \"2023-12-31T23:59:59Z\"",
 529 |             "schema": {
 530 |               "description": "If provided, filters events to include only those with a start date less than or equal to this timestamp Format: ISO-8601 string, e.g., \"2023-12-31T23:59:59Z\"",
 531 |               "type": ["string", "null"]
 532 |             },
 533 |             "style": "form"
 534 |           },
 535 |           {
 536 |             "in": "query",
 537 |             "name": "status",
 538 |             "description": "Filter events by meeting status Valid values: \"upcoming\" (default) returns events after current time, \"past\" returns previous events, \"all\" returns both",
 539 |             "schema": {
 540 |               "description": "Filter events by meeting status Valid values: \"upcoming\" (default) returns events after current time, \"past\" returns previous events, \"all\" returns both",
 541 |               "type": ["string", "null"]
 542 |             },
 543 |             "style": "form"
 544 |           },
 545 |           {
 546 |             "in": "query",
 547 |             "name": "updated_at_gte",
 548 |             "description": "If provided, fetches only events updated at or after this timestamp Format: ISO-8601 string, e.g., \"2023-01-01T00:00:00Z\"",
 549 |             "schema": {
 550 |               "description": "If provided, fetches only events updated at or after this timestamp Format: ISO-8601 string, e.g., \"2023-01-01T00:00:00Z\"",
 551 |               "type": ["string", "null"]
 552 |             },
 553 |             "style": "form"
 554 |           }
 555 |         ],
 556 |         "responses": {
 557 |           "200": {
 558 |             "description": "",
 559 |             "content": {
 560 |               "application/json": { "schema": { "$ref": "#/components/schemas/ListEventResponse" } }
 561 |             }
 562 |           }
 563 |         }
 564 |       }
 565 |     }
 566 |   },
 567 |   "components": {
 568 |     "securitySchemes": {
 569 |       "ApiKeyAuth": {
 570 |         "type": "apiKey",
 571 |         "in": "header",
 572 |         "name": "x-meeting-baas-api-key",
 573 |         "description": "API key for authentication"
 574 |       }
 575 |     },
 576 |     "schemas": {
 577 |       "Account": {
 578 |         "description": "This structure represents the user's account information.",
 579 |         "type": "object",
 580 |         "required": ["created_at", "email", "id", "status"],
 581 |         "properties": {
 582 |           "company_name": { "type": ["string", "null"] },
 583 |           "created_at": { "$ref": "#/components/schemas/SystemTime" },
 584 |           "email": { "type": "string" },
 585 |           "firstname": { "type": ["string", "null"] },
 586 |           "id": { "type": "integer", "format": "int32" },
 587 |           "lastname": { "type": ["string", "null"] },
 588 |           "phone": { "type": ["string", "null"] },
 589 |           "status": { "type": "integer", "format": "int32" }
 590 |         }
 591 |       },
 592 |       "AccountInfos": {
 593 |         "description": "Structure consisting account information.",
 594 |         "type": "object",
 595 |         "required": ["account"],
 596 |         "properties": { "account": { "$ref": "#/components/schemas/Account" } }
 597 |       },
 598 |       "ApiKeyResponse": {
 599 |         "type": "object",
 600 |         "required": ["api_key"],
 601 |         "properties": { "api_key": { "type": "string" } }
 602 |       },
 603 |       "Attendee": {
 604 |         "type": "object",
 605 |         "required": ["email"],
 606 |         "properties": {
 607 |           "email": { "description": "The email address of the meeting attendee", "type": "string" },
 608 |           "name": {
 609 |             "description": "The display name of the attendee if available from the calendar provider (Google, Microsoft)",
 610 |             "type": ["string", "null"]
 611 |           }
 612 |         }
 613 |       },
 614 |       "AudioFrequency": { "type": "string", "enum": ["16khz", "24khz"] },
 615 |       "AutomaticLeaveRequest": {
 616 |         "type": "object",
 617 |         "properties": {
 618 |           "noone_joined_timeout": {
 619 |             "description": "The timeout in seconds for the bot to wait for participants to join before leaving the meeting, defaults to 600 seconds",
 620 |             "type": ["integer", "null"],
 621 |             "format": "uint32",
 622 |             "minimum": 0.0
 623 |           },
 624 |           "waiting_room_timeout": {
 625 |             "description": "The timeout in seconds for the bot to wait in the waiting room before leaving the meeting, defaults to 600 seconds",
 626 |             "type": ["integer", "null"],
 627 |             "format": "uint32",
 628 |             "minimum": 0.0
 629 |           }
 630 |         }
 631 |       },
 632 |       "Bot": {
 633 |         "type": "object",
 634 |         "required": ["bot", "duration", "params"],
 635 |         "properties": {
 636 |           "bot": { "$ref": "#/components/schemas/Bot2" },
 637 |           "duration": { "type": "integer", "format": "int64" },
 638 |           "params": { "$ref": "#/components/schemas/BotParam" }
 639 |         }
 640 |       },
 641 |       "Bot2": {
 642 |         "type": "object",
 643 |         "required": [
 644 |           "account_id",
 645 |           "bot_param_id",
 646 |           "created_at",
 647 |           "diarization_v2",
 648 |           "ended_at",
 649 |           "id",
 650 |           "meeting_url",
 651 |           "mp4_s3_path",
 652 |           "reserved",
 653 |           "uuid"
 654 |         ],
 655 |         "properties": {
 656 |           "account_id": { "type": "integer", "format": "int32" },
 657 |           "bot_param_id": { "type": "integer", "format": "int32" },
 658 |           "created_at": { "$ref": "#/components/schemas/DateTime" },
 659 |           "diarization_v2": { "type": "boolean" },
 660 |           "ended_at": { "$ref": "#/components/schemas/OptionalDateTime" },
 661 |           "errors": { "type": ["string", "null"] },
 662 |           "event_id": { "type": ["integer", "null"], "format": "int32" },
 663 |           "id": { "type": "integer", "format": "int32" },
 664 |           "meeting_url": { "type": "string" },
 665 |           "mp4_s3_path": { "type": "string" },
 666 |           "reserved": { "type": "boolean" },
 667 |           "scheduled_bot_id": { "type": ["integer", "null"], "format": "int32" },
 668 |           "session_id": { "type": ["string", "null"] },
 669 |           "uuid": { "type": "string", "format": "uuid" }
 670 |         }
 671 |       },
 672 |       "BotData": {
 673 |         "type": "object",
 674 |         "required": ["bot", "transcripts"],
 675 |         "properties": {
 676 |           "bot": { "$ref": "#/components/schemas/BotWithParams" },
 677 |           "transcripts": { "type": "array", "items": { "$ref": "#/components/schemas/Transcript" } }
 678 |         }
 679 |       },
 680 |       "BotPagined": {
 681 |         "type": "object",
 682 |         "required": ["bots", "has_more"],
 683 |         "properties": {
 684 |           "bots": { "type": "array", "items": { "$ref": "#/components/schemas/Bot" } },
 685 |           "has_more": { "type": "boolean" }
 686 |         }
 687 |       },
 688 |       "BotParam": {
 689 |         "type": "object",
 690 |         "required": ["bot_name", "extra", "webhook_url"],
 691 |         "properties": {
 692 |           "bot_image": { "type": ["string", "null"] },
 693 |           "bot_name": { "type": "string" },
 694 |           "deduplication_key": { "type": ["string", "null"] },
 695 |           "enter_message": { "type": ["string", "null"] },
 696 |           "extra": { "$ref": "#/components/schemas/Extra" },
 697 |           "noone_joined_timeout": { "type": ["integer", "null"], "format": "int32" },
 698 |           "recording_mode": {
 699 |             "anyOf": [{ "$ref": "#/components/schemas/RecordingMode" }, { "type": "null" }]
 700 |           },
 701 |           "speech_to_text": {
 702 |             "anyOf": [{ "$ref": "#/components/schemas/SpeechToText2" }, { "type": "null" }]
 703 |           },
 704 |           "streaming_audio_frequency": {
 705 |             "anyOf": [{ "$ref": "#/components/schemas/AudioFrequency" }, { "type": "null" }]
 706 |           },
 707 |           "streaming_input": { "type": ["string", "null"] },
 708 |           "streaming_output": { "type": ["string", "null"] },
 709 |           "waiting_room_timeout": { "type": ["integer", "null"], "format": "int32" },
 710 |           "webhook_url": { "type": "string" }
 711 |         }
 712 |       },
 713 |       "BotParam2": {
 714 |         "type": "object",
 715 |         "required": ["bot_name", "extra"],
 716 |         "properties": {
 717 |           "bot_image": { "type": ["string", "null"] },
 718 |           "bot_name": { "type": "string" },
 719 |           "deduplication_key": { "type": ["string", "null"] },
 720 |           "enter_message": { "type": ["string", "null"] },
 721 |           "extra": { "$ref": "#/components/schemas/Extra" },
 722 |           "noone_joined_timeout": { "type": ["integer", "null"], "format": "int32" },
 723 |           "recording_mode": {
 724 |             "anyOf": [{ "$ref": "#/components/schemas/RecordingMode" }, { "type": "null" }]
 725 |           },
 726 |           "speech_to_text": {
 727 |             "anyOf": [{ "$ref": "#/components/schemas/SpeechToText" }, { "type": "null" }]
 728 |           },
 729 |           "streaming_audio_frequency": {
 730 |             "anyOf": [{ "$ref": "#/components/schemas/AudioFrequency" }, { "type": "null" }]
 731 |           },
 732 |           "streaming_input": { "type": ["string", "null"] },
 733 |           "streaming_output": { "type": ["string", "null"] },
 734 |           "waiting_room_timeout": { "type": ["integer", "null"], "format": "int32" },
 735 |           "webhook_url": { "type": ["string", "null"] }
 736 |         }
 737 |       },
 738 |       "BotParam3": {
 739 |         "type": "object",
 740 |         "properties": {
 741 |           "bot_image": { "default": null, "type": ["string", "null"] },
 742 |           "bot_name": { "type": ["string", "null"] },
 743 |           "deduplication_key": { "default": null, "type": ["string", "null"] },
 744 |           "enter_message": { "default": null, "type": ["string", "null"] },
 745 |           "extra": { "default": null },
 746 |           "noone_joined_timeout": {
 747 |             "default": null,
 748 |             "type": ["integer", "null"],
 749 |             "format": "int32"
 750 |           },
 751 |           "recording_mode": {
 752 |             "anyOf": [
 753 |               { "anyOf": [{ "$ref": "#/components/schemas/RecordingMode" }, { "type": "null" }] },
 754 |               { "type": "null" }
 755 |             ]
 756 |           },
 757 |           "speech_to_text": {
 758 |             "default": null,
 759 |             "anyOf": [
 760 |               { "anyOf": [{ "$ref": "#/components/schemas/SpeechToText" }, { "type": "null" }] },
 761 |               { "type": "null" }
 762 |             ]
 763 |           },
 764 |           "streaming_audio_frequency": {
 765 |             "default": null,
 766 |             "anyOf": [
 767 |               { "anyOf": [{ "$ref": "#/components/schemas/AudioFrequency" }, { "type": "null" }] },
 768 |               { "type": "null" }
 769 |             ]
 770 |           },
 771 |           "streaming_input": { "default": null, "type": ["string", "null"] },
 772 |           "streaming_output": { "default": null, "type": ["string", "null"] },
 773 |           "waiting_room_timeout": {
 774 |             "default": null,
 775 |             "type": ["integer", "null"],
 776 |             "format": "int32"
 777 |           },
 778 |           "webhook_url": { "type": ["string", "null"] }
 779 |         }
 780 |       },
 781 |       "BotWithParams": {
 782 |         "type": "object",
 783 |         "required": [
 784 |           "account_id",
 785 |           "bot_name",
 786 |           "bot_param_id",
 787 |           "created_at",
 788 |           "diarization_v2",
 789 |           "ended_at",
 790 |           "extra",
 791 |           "id",
 792 |           "meeting_url",
 793 |           "mp4_s3_path",
 794 |           "reserved",
 795 |           "uuid",
 796 |           "webhook_url"
 797 |         ],
 798 |         "properties": {
 799 |           "account_id": { "type": "integer", "format": "int32" },
 800 |           "bot_image": { "type": ["string", "null"] },
 801 |           "bot_name": { "type": "string" },
 802 |           "bot_param_id": { "type": "integer", "format": "int32" },
 803 |           "created_at": { "$ref": "#/components/schemas/DateTime" },
 804 |           "deduplication_key": { "type": ["string", "null"] },
 805 |           "diarization_v2": { "type": "boolean" },
 806 |           "ended_at": { "$ref": "#/components/schemas/OptionalDateTime" },
 807 |           "enter_message": { "type": ["string", "null"] },
 808 |           "errors": { "type": ["string", "null"] },
 809 |           "event_id": { "type": ["integer", "null"], "format": "int32" },
 810 |           "extra": { "$ref": "#/components/schemas/Extra" },
 811 |           "id": { "type": "integer", "format": "int32" },
 812 |           "meeting_url": { "type": "string" },
 813 |           "mp4_s3_path": { "type": "string" },
 814 |           "noone_joined_timeout": { "type": ["integer", "null"], "format": "int32" },
 815 |           "recording_mode": {
 816 |             "anyOf": [{ "$ref": "#/components/schemas/RecordingMode" }, { "type": "null" }]
 817 |           },
 818 |           "reserved": { "type": "boolean" },
 819 |           "scheduled_bot_id": { "type": ["integer", "null"], "format": "int32" },
 820 |           "session_id": { "type": ["string", "null"] },
 821 |           "speech_to_text_api_key": { "type": ["string", "null"] },
 822 |           "speech_to_text_provider": {
 823 |             "anyOf": [{ "$ref": "#/components/schemas/SpeechToTextProvider" }, { "type": "null" }]
 824 |           },
 825 |           "streaming_audio_frequency": {
 826 |             "anyOf": [{ "$ref": "#/components/schemas/AudioFrequency" }, { "type": "null" }]
 827 |           },
 828 |           "streaming_input": { "type": ["string", "null"] },
 829 |           "streaming_output": { "type": ["string", "null"] },
 830 |           "uuid": { "type": "string", "format": "uuid" },
 831 |           "waiting_room_timeout": { "type": ["integer", "null"], "format": "int32" },
 832 |           "webhook_url": { "type": "string" }
 833 |         }
 834 |       },
 835 |       "Calendar": {
 836 |         "type": "object",
 837 |         "required": ["email", "google_id", "name", "uuid"],
 838 |         "properties": {
 839 |           "email": { "type": "string" },
 840 |           "google_id": { "type": "string" },
 841 |           "name": { "type": "string" },
 842 |           "resource_id": { "type": ["string", "null"] },
 843 |           "uuid": { "type": "string", "format": "uuid" }
 844 |         }
 845 |       },
 846 |       "CalendarListEntry": {
 847 |         "type": "object",
 848 |         "required": ["email", "id", "is_primary"],
 849 |         "properties": {
 850 |           "email": { "type": "string" },
 851 |           "id": { "type": "string" },
 852 |           "is_primary": { "type": "boolean" }
 853 |         }
 854 |       },
 855 |       "CreateCalendarParams": {
 856 |         "type": "object",
 857 |         "required": ["oauth_client_id", "oauth_client_secret", "oauth_refresh_token", "platform"],
 858 |         "properties": {
 859 |           "oauth_client_id": { "type": "string" },
 860 |           "oauth_client_secret": { "type": "string" },
 861 |           "oauth_refresh_token": { "type": "string" },
 862 |           "platform": { "$ref": "#/components/schemas/Provider" },
 863 |           "raw_calendar_id": { "type": ["string", "null"] }
 864 |         }
 865 |       },
 866 |       "CreateCalendarResponse": {
 867 |         "type": "object",
 868 |         "required": ["calendar"],
 869 |         "properties": { "calendar": { "$ref": "#/components/schemas/Calendar" } }
 870 |       },
 871 |       "DailyTokenConsumption": {
 872 |         "type": "object",
 873 |         "required": ["consumption_by_service", "date"],
 874 |         "properties": {
 875 |           "consumption_by_service": { "$ref": "#/components/schemas/TokenConsumptionByService" },
 876 |           "date": { "type": "string" }
 877 |         }
 878 |       },
 879 |       "DateTime": { "type": "string", "format": "date-time" },
 880 |       "DeleteResponse": {
 881 |         "type": "object",
 882 |         "required": ["ok", "status"],
 883 |         "properties": {
 884 |           "ok": {
 885 |             "description": "Whether the request was processed successfully",
 886 |             "type": "boolean"
 887 |           },
 888 |           "status": {
 889 |             "description": "The detailed status of the deletion operation",
 890 |             "$ref": "#/components/schemas/DeleteStatus"
 891 |           }
 892 |         }
 893 |       },
 894 |       "DeleteStatus": {
 895 |         "oneOf": [
 896 |           {
 897 |             "description": "All data was successfully deleted",
 898 |             "type": "string",
 899 |             "enum": ["deleted"]
 900 |           },
 901 |           {
 902 |             "description": "Some data was deleted, but other parts couldn't be removed",
 903 |             "type": "string",
 904 |             "enum": ["partiallyDeleted"]
 905 |           },
 906 |           {
 907 |             "description": "No data needed to be deleted as it was already removed",
 908 |             "type": "string",
 909 |             "enum": ["alreadyDeleted"]
 910 |           },
 911 |           {
 912 |             "description": "No data was found for the specified bot",
 913 |             "type": "string",
 914 |             "enum": ["noDataFound"]
 915 |           }
 916 |         ]
 917 |       },
 918 |       "EndMeetingQuery": {
 919 |         "type": "object",
 920 |         "required": ["bot_uuid"],
 921 |         "properties": { "bot_uuid": { "type": "string" } }
 922 |       },
 923 |       "EndMeetingTrampolineQuery": {
 924 |         "type": "object",
 925 |         "required": ["bot_uuid"],
 926 |         "properties": { "bot_uuid": { "type": "string" } }
 927 |       },
 928 |       "EndMeetingTrampolineRequest": {
 929 |         "type": "object",
 930 |         "required": ["diarization_v2"],
 931 |         "properties": { "diarization_v2": { "type": "boolean" } }
 932 |       },
 933 |       "Event": {
 934 |         "type": "object",
 935 |         "required": [
 936 |           "attendees",
 937 |           "calendar_uuid",
 938 |           "deleted",
 939 |           "end_time",
 940 |           "google_id",
 941 |           "is_organizer",
 942 |           "is_recurring",
 943 |           "last_updated_at",
 944 |           "meeting_url",
 945 |           "name",
 946 |           "raw",
 947 |           "start_time",
 948 |           "uuid"
 949 |         ],
 950 |         "properties": {
 951 |           "attendees": { "type": "array", "items": { "$ref": "#/components/schemas/Attendee" } },
 952 |           "bot_param": {
 953 |             "description": "Associated bot parameters if a bot is scheduled for this event",
 954 |             "anyOf": [{ "$ref": "#/components/schemas/BotParam" }, { "type": "null" }]
 955 |           },
 956 |           "calendar_uuid": { "type": "string", "format": "uuid" },
 957 |           "deleted": {
 958 |             "description": "Indicates whether this event has been deleted",
 959 |             "type": "boolean"
 960 |           },
 961 |           "end_time": {
 962 |             "description": "The end time of the event in UTC timezone",
 963 |             "type": "string",
 964 |             "format": "date-time"
 965 |           },
 966 |           "google_id": {
 967 |             "description": "The unique identifier of the event from the calendar provider (Google, Microsoft)",
 968 |             "type": "string"
 969 |           },
 970 |           "is_organizer": {
 971 |             "description": "Indicates whether the current user is the organizer of this event",
 972 |             "type": "boolean"
 973 |           },
 974 |           "is_recurring": {
 975 |             "description": "Indicates whether this event is part of a recurring series",
 976 |             "type": "boolean"
 977 |           },
 978 |           "last_updated_at": {
 979 |             "description": "The timestamp when this event was last updated",
 980 |             "type": "string",
 981 |             "format": "date-time"
 982 |           },
 983 |           "meeting_url": {
 984 |             "description": "The URL that can be used to join the meeting (if available)",
 985 |             "type": "string"
 986 |           },
 987 |           "name": { "description": "The title/name of the calendar event", "type": "string" },
 988 |           "raw": {
 989 |             "description": "The raw calendar data from the provider in JSON format",
 990 |             "$ref": "#/components/schemas/Extra"
 991 |           },
 992 |           "recurring_event_id": {
 993 |             "description": "For recurring events, the ID of the parent recurring event series (if applicable)",
 994 |             "type": ["string", "null"]
 995 |           },
 996 |           "start_time": {
 997 |             "description": "The start time of the event in UTC timezone",
 998 |             "type": "string",
 999 |             "format": "date-time"
1000 |           },
1001 |           "uuid": { "type": "string", "format": "uuid" }
1002 |         }
1003 |       },
1004 |       "Extra": { "description": "Custom data object", "additionalProperties": true },
1005 |       "FailedRecordRequest": {
1006 |         "type": "object",
1007 |         "required": ["meeting_url", "message"],
1008 |         "properties": { "meeting_url": { "type": "string" }, "message": { "type": "string" } }
1009 |       },
1010 |       "GetAllBotsQuery": {
1011 |         "type": "object",
1012 |         "required": ["limit", "offset"],
1013 |         "properties": {
1014 |           "bot_id": { "type": ["string", "null"] },
1015 |           "end_date": { "type": ["string", "null"], "format": "partial-date-time" },
1016 |           "limit": { "type": "integer", "format": "int32" },
1017 |           "offset": { "type": "integer", "format": "int32" },
1018 |           "start_date": { "type": ["string", "null"], "format": "partial-date-time" }
1019 |         }
1020 |       },
1021 |       "GetMeetingDataQuery": {
1022 |         "type": "object",
1023 |         "required": ["bot_id"],
1024 |         "properties": { "bot_id": { "type": "string" } }
1025 |       },
1026 |       "GetStartedAccount": {
1027 |         "type": "object",
1028 |         "required": ["email"],
1029 |         "properties": {
1030 |           "email": { "type": "string" },
1031 |           "firstname": { "type": ["string", "null"] },
1032 |           "google_token_id": { "type": ["string", "null"] },
1033 |           "lastname": { "type": ["string", "null"] },
1034 |           "microsoft_token_id": { "type": ["string", "null"] }
1035 |         }
1036 |       },
1037 |       "GetWebhookUrlResponse": {
1038 |         "type": "object",
1039 |         "properties": { "webhook_url": { "type": ["string", "null"] } }
1040 |       },
1041 |       "GetstartedQuery": {
1042 |         "type": "object",
1043 |         "properties": { "redirect_url": { "type": ["string", "null"] } }
1044 |       },
1045 |       "JoinRequest": {
1046 |         "type": "object",
1047 |         "required": ["bot_name", "meeting_url", "reserved"],
1048 |         "properties": {
1049 |           "automatic_leave": {
1050 |             "description": "The bot will leave the meeting automatically after the timeout, defaults to 600 seconds.",
1051 |             "anyOf": [{ "$ref": "#/components/schemas/AutomaticLeaveRequest" }, { "type": "null" }]
1052 |           },
1053 |           "bot_image": {
1054 |             "description": "The image to use for the bot, must be a URL. Recommended ratio is 4:3.",
1055 |             "type": ["string", "null"],
1056 |             "format": "uri"
1057 |           },
1058 |           "bot_name": { "type": "string" },
1059 |           "deduplication_key": {
1060 |             "description": "We prevent multiple bots with same API key joining a meeting within 5 mins, unless overridden by deduplication_key.",
1061 |             "type": ["string", "null"]
1062 |           },
1063 |           "entry_message": {
1064 |             "description": "There are no entry messages on Microsoft Teams as guests outside of an organization do not have access to the chat.",
1065 |             "type": ["string", "null"]
1066 |           },
1067 |           "extra": {
1068 |             "description": "A Json object that allows you to add custom data to a bot for your convenience, e.g. your end user's ID.",
1069 |             "default": null,
1070 |             "$ref": "#/components/schemas/Extra"
1071 |           },
1072 |           "meeting_url": { "type": "string" },
1073 |           "recording_mode": {
1074 |             "description": "The recording mode for the bot, defaults to 'speaker_view'.",
1075 |             "anyOf": [{ "$ref": "#/components/schemas/RecordingMode" }, { "type": "null" }]
1076 |           },
1077 |           "reserved": {
1078 |             "description": "Whether or not the bot should come from the available pool of bots or be a dedicated bot. Reserved bots come in exactly 4 minutes after the request.",
1079 |             "type": "boolean"
1080 |           },
1081 |           "speech_to_text": {
1082 |             "description": "The default speech to text provider is Gladia.",
1083 |             "anyOf": [{ "$ref": "#/components/schemas/SpeechToText" }, { "type": "null" }]
1084 |           },
1085 |           "start_time": {
1086 |             "description": "Unix timestamp (in milliseconds) for when the bot should join the meeting. The bot joins 4 minutes before the start time.",
1087 |             "type": ["integer", "null"],
1088 |             "format": "uint64",
1089 |             "minimum": 0.0
1090 |           },
1091 |           "streaming": {
1092 |             "description": "WebSocket streams for 16kHz audio. Input stream receives audio sent to the bot. Output stream receives audio from the bot.",
1093 |             "anyOf": [{ "$ref": "#/components/schemas/StreamingApiParameter" }, { "type": "null" }]
1094 |           },
1095 |           "webhook_url": {
1096 |             "description": "A webhook URL to send events to, overrides the webhook URL set in your account settings.",
1097 |             "type": ["string", "null"]
1098 |           }
1099 |         },
1100 |         "additionalProperties": false
1101 |       },
1102 |       "JoinRequestScheduled": {
1103 |         "type": "object",
1104 |         "required": ["bot_param_id", "meeting_url", "schedule_origin"],
1105 |         "properties": {
1106 |           "bot_param_id": { "type": "integer", "format": "int32" },
1107 |           "meeting_url": { "type": "string" },
1108 |           "schedule_origin": { "$ref": "#/components/schemas/ScheduleOrigin" }
1109 |         },
1110 |         "additionalProperties": false
1111 |       },
1112 |       "JoinResponse": {
1113 |         "type": "object",
1114 |         "required": ["bot_id"],
1115 |         "properties": { "bot_id": { "type": "string", "format": "uuid" } }
1116 |       },
1117 |       "JoinResponse2": {
1118 |         "type": "object",
1119 |         "required": ["bot_id"],
1120 |         "properties": { "bot_id": { "type": "string", "format": "uuid" } }
1121 |       },
1122 |       "LeaveResponse": {
1123 |         "type": "object",
1124 |         "required": ["ok"],
1125 |         "properties": { "ok": { "type": "boolean" } }
1126 |       },
1127 |       "ListEventResponse": {
1128 |         "type": "object",
1129 |         "required": ["data"],
1130 |         "properties": {
1131 |           "data": {
1132 |             "description": "Vector of calendar events matching the list criteria",
1133 |             "type": "array",
1134 |             "items": { "$ref": "#/components/schemas/Event" }
1135 |           },
1136 |           "next": {
1137 |             "description": "Optional url for fetching the next page of results if there are more results to fetch. The limit of events returned is 100. When None, there are no more results to fetch.",
1138 |             "type": ["string", "null"]
1139 |           }
1140 |         }
1141 |       },
1142 |       "ListRawCalendarsParams": {
1143 |         "type": "object",
1144 |         "required": ["oauth_client_id", "oauth_client_secret", "oauth_refresh_token", "platform"],
1145 |         "properties": {
1146 |           "oauth_client_id": { "type": "string" },
1147 |           "oauth_client_secret": { "type": "string" },
1148 |           "oauth_refresh_token": { "type": "string" },
1149 |           "platform": { "$ref": "#/components/schemas/Provider" }
1150 |         }
1151 |       },
1152 |       "ListRawCalendarsResponse": {
1153 |         "type": "object",
1154 |         "required": ["calendars"],
1155 |         "properties": {
1156 |           "calendars": {
1157 |             "type": "array",
1158 |             "items": { "$ref": "#/components/schemas/CalendarListEntry" }
1159 |           }
1160 |         }
1161 |       },
1162 |       "ListRecentBotsQuery": {
1163 |         "type": "object",
1164 |         "properties": {
1165 |           "bot_name": {
1166 |             "description": "Filter bots by name containing this string.\n\nPerforms a case-insensitive partial match on the bot's name. Useful for finding bots with specific naming conventions or to locate a particular bot when you don't have its ID.\n\nExample: \"Sales\" would match \"Sales Meeting\", \"Quarterly Sales\", etc.",
1167 |             "type": ["string", "null"]
1168 |           },
1169 |           "created_after": {
1170 |             "description": "Filter bots created after this date (ISO format).\n\nLimits results to bots created at or after the specified timestamp. Used for time-based filtering to find recent additions.\n\nFormat: ISO-8601 date-time string (YYYY-MM-DDThh:mm:ss) Example: \"2023-05-01T00:00:00\"",
1171 |             "type": ["string", "null"]
1172 |           },
1173 |           "created_before": {
1174 |             "description": "Filter bots created before this date (ISO format).\n\nLimits results to bots created at or before the specified timestamp. Used for time-based filtering to exclude recent additions.\n\nFormat: ISO-8601 date-time string (YYYY-MM-DDThh:mm:ss) Example: \"2023-05-31T23:59:59\"",
1175 |             "type": ["string", "null"]
1176 |           },
1177 |           "cursor": {
1178 |             "description": "Cursor for pagination, obtained from previous response.\n\nUsed for retrieving the next set of results after a previous call. The cursor value is returned in the `nextCursor` field of responses that have additional results available.\n\nFormat: Base64-encoded string containing pagination metadata",
1179 |             "type": ["string", "null"]
1180 |           },
1181 |           "filter_by_extra": {
1182 |             "description": "Filter bots by matching values in the extra JSON payload.\n\nThis parameter performs in-memory filtering on the `extra` JSON field, similar to a SQL WHERE clause. It reduces the result set to only include bots that match all specified conditions.\n\nFormat specifications: - Single condition: \"field:value\" - Multiple conditions: \"field1:value1,field2:value2\"\n\nExamples: - \"customer_id:12345\" - Only bots with this customer ID - \"status:active,project:sales\" - Only active bots from sales projects\n\nNotes: - All conditions must match for a bot to be included - Values are matched exactly (case-sensitive) - Bots without the specified field are excluded",
1183 |             "type": ["string", "null"]
1184 |           },
1185 |           "limit": {
1186 |             "description": "Maximum number of bots to return in a single request.\n\nLimits the number of results returned in a single API call. This parameter helps control response size and page length.\n\nDefault: 10 Minimum: 1 Maximum: 50",
1187 |             "default": 10,
1188 |             "type": "integer",
1189 |             "format": "int32"
1190 |           },
1191 |           "meeting_url": {
1192 |             "description": "Filter bots by meeting URL containing this string.\n\nPerforms a case-insensitive partial match on the bot's meeting URL. Use this to find bots associated with specific meeting platforms or particular meeting IDs.\n\nExample: \"zoom.us\" would match all Zoom meetings",
1193 |             "type": ["string", "null"]
1194 |           },
1195 |           "sort_by_extra": {
1196 |             "description": "Sort the results by a field in the extra JSON payload.\n\nThis parameter performs in-memory sorting on the `extra` JSON field, similar to a SQL ORDER BY clause. It changes the order of results but not which results are included.\n\nFormat specifications: - Default (ascending): \"field\" - Explicit direction: \"field:asc\" or \"field:desc\"\n\nExamples: - \"customer_id\" - Sort by customer_id (ascending) - \"priority:desc\" - Sort by priority (descending)\n\nNotes: - Applied after all filtering - String comparison is used for sorting - Bots with the field come before bots without it - Can be combined with filter_by_extra",
1197 |             "type": ["string", "null"]
1198 |           },
1199 |           "speaker_name": {
1200 |             "description": "NOTE: this is a preview feature and not yet available\n\nFilter bots by speaker name containing this string.\n\nPerforms a case-insensitive partial match on the speakers in the meeting. Useful for finding meetings that included a specific person.\n\nExample: \"John\" would match meetings with speakers like \"John Smith\" or \"John Doe\"",
1201 |             "type": ["string", "null"]
1202 |           }
1203 |         }
1204 |       },
1205 |       "ListRecentBotsResponse": {
1206 |         "type": "object",
1207 |         "required": ["lastUpdated", "recentBots"],
1208 |         "properties": {
1209 |           "lastUpdated": {
1210 |             "description": "Timestamp of when this data was generated",
1211 |             "type": "string"
1212 |           },
1213 |           "nextCursor": {
1214 |             "description": "Cursor for fetching the next page, null if no more results",
1215 |             "type": ["string", "null"]
1216 |           },
1217 |           "recentBots": {
1218 |             "description": "List of recent bots with metadata",
1219 |             "type": "array",
1220 |             "items": { "$ref": "#/components/schemas/RecentBotEntry" }
1221 |           }
1222 |         }
1223 |       },
1224 |       "LoginAccount": {
1225 |         "type": "object",
1226 |         "required": ["password", "pseudo"],
1227 |         "properties": {
1228 |           "app_signin_token": { "type": ["string", "null"] },
1229 |           "google_chrome_token_id": { "type": ["string", "null"] },
1230 |           "google_token_id": { "type": ["string", "null"] },
1231 |           "microsoft_token_id": { "type": ["string", "null"] },
1232 |           "password": { "type": "string" },
1233 |           "pseudo": { "type": "string" }
1234 |         }
1235 |       },
1236 |       "LoginQuery": {
1237 |         "type": "object",
1238 |         "properties": { "redirect_url": { "type": ["string", "null"] } }
1239 |       },
1240 |       "Metadata": {
1241 |         "type": "object",
1242 |         "required": ["bot_data", "content_deleted", "duration", "mp4"],
1243 |         "properties": {
1244 |           "bot_data": { "$ref": "#/components/schemas/BotData" },
1245 |           "content_deleted": {
1246 |             "description": "Indicates whether the recording has been deleted",
1247 |             "type": "boolean"
1248 |           },
1249 |           "duration": {
1250 |             "description": "Duration of the recording in seconds",
1251 |             "type": "integer",
1252 |             "format": "int64"
1253 |           },
1254 |           "mp4": {
1255 |             "description": "URL to access the recording MP4 file. Will be null if content has been deleted.",
1256 |             "type": "string"
1257 |           }
1258 |         }
1259 |       },
1260 |       "OptionalDateTime": { "type": ["string", "null"], "format": "date-time" },
1261 |       "PostWebhookUrlRequest": {
1262 |         "type": "object",
1263 |         "required": ["webhook_url"],
1264 |         "properties": { "webhook_url": { "type": "string" } }
1265 |       },
1266 |       "Provider": {
1267 |         "description": "Fields with value `\"simple\"` parse as `Kind::Simple`. Fields with value `\"fancy\"` parse as `Kind::SoFancy`.",
1268 |         "type": "string",
1269 |         "enum": ["Google", "Microsoft"]
1270 |       },
1271 |       "QueryListEvent": {
1272 |         "type": "object",
1273 |         "required": ["calendar_id"],
1274 |         "properties": {
1275 |           "attendee_email": {
1276 |             "description": "If provided, filters events to include only those with this attendee's email address Example: \"[email protected]\"",
1277 |             "type": ["string", "null"]
1278 |           },
1279 |           "calendar_id": {
1280 |             "description": "Calendar ID to filter events by This is required to specify which calendar's events to retrieve",
1281 |             "type": "string"
1282 |           },
1283 |           "cursor": {
1284 |             "description": "Optional cursor for pagination This value is included in the `next` field of the previous response",
1285 |             "type": ["string", "null"]
1286 |           },
1287 |           "organizer_email": {
1288 |             "description": "If provided, filters events to include only those with this organizer's email address Example: \"[email protected]\"",
1289 |             "type": ["string", "null"]
1290 |           },
1291 |           "start_date_gte": {
1292 |             "description": "If provided, filters events to include only those with a start date greater than or equal to this timestamp Format: ISO-8601 string, e.g., \"2023-01-01T00:00:00Z\"",
1293 |             "type": ["string", "null"]
1294 |           },
1295 |           "start_date_lte": {
1296 |             "description": "If provided, filters events to include only those with a start date less than or equal to this timestamp Format: ISO-8601 string, e.g., \"2023-12-31T23:59:59Z\"",
1297 |             "type": ["string", "null"]
1298 |           },
1299 |           "status": {
1300 |             "description": "Filter events by meeting status Valid values: \"upcoming\" (default) returns events after current time, \"past\" returns previous events, \"all\" returns both",
1301 |             "type": ["string", "null"]
1302 |           },
1303 |           "updated_at_gte": {
1304 |             "description": "If provided, fetches only events updated at or after this timestamp Format: ISO-8601 string, e.g., \"2023-01-01T00:00:00Z\"",
1305 |             "type": ["string", "null"]
1306 |           }
1307 |         }
1308 |       },
1309 |       "QueryPatchRecordEvent": {
1310 |         "type": "object",
1311 |         "properties": {
1312 |           "all_occurrences": {
1313 |             "description": "schedule a bot to all occurences of a recurring event",
1314 |             "type": ["boolean", "null"]
1315 |           }
1316 |         }
1317 |       },
1318 |       "QueryScheduleRecordEvent": {
1319 |         "type": "object",
1320 |         "properties": {
1321 |           "all_occurrences": {
1322 |             "description": "schedule a bot to all occurences of a recurring event",
1323 |             "type": ["boolean", "null"]
1324 |           }
1325 |         }
1326 |       },
1327 |       "QueryUnScheduleRecordEvent": {
1328 |         "type": "object",
1329 |         "properties": {
1330 |           "all_occurrences": {
1331 |             "description": "unschedule a bot from all occurences of a recurring event",
1332 |             "type": ["boolean", "null"]
1333 |           }
1334 |         }
1335 |       },
1336 |       "ReceivedMessageQuery": {
1337 |         "type": "object",
1338 |         "required": ["session_id"],
1339 |         "properties": { "session_id": { "type": "string" } }
1340 |       },
1341 |       "RecentBotEntry": {
1342 |         "type": "object",
1343 |         "required": [
1344 |           "content_deleted",
1345 |           "createdAt",
1346 |           "extra",
1347 |           "id",
1348 |           "meetingUrl",
1349 |           "name",
1350 |           "speakers"
1351 |         ],
1352 |         "properties": {
1353 |           "accessCount": {
1354 |             "description": "Number of times this bot data has been accessed (if tracked)",
1355 |             "type": ["integer", "null"],
1356 |             "format": "int32"
1357 |           },
1358 |           "content_deleted": {
1359 |             "description": "Indicates whether the recording content has been deleted",
1360 |             "type": "boolean"
1361 |           },
1362 |           "createdAt": { "description": "Creation timestamp of the bot", "type": "string" },
1363 |           "duration": {
1364 |             "description": "Duration of the bot session in seconds (if completed)",
1365 |             "type": ["integer", "null"],
1366 |             "format": "int64"
1367 |           },
1368 |           "endedAt": {
1369 |             "description": "End time of the bot session (if completed)",
1370 |             "type": ["string", "null"]
1371 |           },
1372 |           "extra": {
1373 |             "description": "Extra custom data provided during bot creation",
1374 |             "$ref": "#/components/schemas/Extra"
1375 |           },
1376 |           "id": {
1377 |             "description": "Unique identifier of the bot",
1378 |             "type": "string",
1379 |             "format": "uuid"
1380 |           },
1381 |           "lastAccessedAt": {
1382 |             "description": "Last time this bot data was accessed (if available)",
1383 |             "type": ["string", "null"]
1384 |           },
1385 |           "meetingUrl": { "description": "URL of the meeting the bot joined", "type": "string" },
1386 |           "name": { "description": "Name of the bot", "type": "string" },
1387 |           "sessionId": {
1388 |             "description": "Session ID if the bot is active",
1389 |             "type": ["string", "null"]
1390 |           },
1391 |           "speakers": {
1392 |             "description": "List of unique speaker names from the bot's transcripts",
1393 |             "type": "array",
1394 |             "items": { "type": "string" }
1395 |           }
1396 |         }
1397 |       },
1398 |       "RecognizerWord": {
1399 |         "type": "object",
1400 |         "required": ["end_time", "start_time", "text"],
1401 |         "properties": {
1402 |           "end_time": { "type": "number", "format": "double" },
1403 |           "start_time": { "type": "number", "format": "double" },
1404 |           "text": { "type": "string" },
1405 |           "user_id": { "type": ["integer", "null"], "format": "int32" }
1406 |         }
1407 |       },
1408 |       "RecordingMode": {
1409 |         "description": "Recording mode for the bot",
1410 |         "oneOf": [
1411 |           {
1412 |             "description": "Records the active speaker view",
1413 |             "type": "string",
1414 |             "enum": ["speaker_view"]
1415 |           },
1416 |           {
1417 |             "description": "Records the gallery view showing multiple participants",
1418 |             "type": "string",
1419 |             "enum": ["gallery_view"]
1420 |           },
1421 |           {
1422 |             "description": "Records only the audio from the meeting",
1423 |             "type": "string",
1424 |             "enum": ["audio_only"]
1425 |           }
1426 |         ]
1427 |       },
1428 |       "ResyncAllCalendarsResponse": {
1429 |         "type": "object",
1430 |         "required": ["errors", "synced_calendars"],
1431 |         "properties": {
1432 |           "errors": {
1433 |             "description": "List of calendar UUIDs that failed to resync, with detailed error messages explaining the failure reason",
1434 |             "type": "array",
1435 |             "items": {
1436 |               "type": "array",
1437 |               "items": [{ "type": "string", "format": "uuid" }, { "type": "string" }],
1438 |               "maxItems": 2,
1439 |               "minItems": 2
1440 |             }
1441 |           },
1442 |           "synced_calendars": {
1443 |             "description": "List of calendar UUIDs that were successfully resynced with their calendar provider (Google, Microsoft)",
1444 |             "type": "array",
1445 |             "items": { "type": "string", "format": "uuid" }
1446 |           }
1447 |         }
1448 |       },
1449 |       "ResyncAllResponse": {
1450 |         "type": "object",
1451 |         "required": ["errors", "synced_calendars"],
1452 |         "properties": {
1453 |           "errors": {
1454 |             "description": "List of calendar UUIDs that failed to resync, with error messages",
1455 |             "type": "array",
1456 |             "items": {
1457 |               "type": "array",
1458 |               "items": [{ "type": "string", "format": "uuid" }, { "type": "string" }],
1459 |               "maxItems": 2,
1460 |               "minItems": 2
1461 |             }
1462 |           },
1463 |           "synced_calendars": {
1464 |             "description": "List of calendar UUIDs that were successfully resynced",
1465 |             "type": "array",
1466 |             "items": { "type": "string", "format": "uuid" }
1467 |           }
1468 |         }
1469 |       },
1470 |       "RetranscribeBody": {
1471 |         "type": "object",
1472 |         "required": ["bot_uuid"],
1473 |         "properties": {
1474 |           "bot_uuid": { "type": "string" },
1475 |           "speech_to_text": {
1476 |             "anyOf": [{ "$ref": "#/components/schemas/SpeechToText" }, { "type": "null" }]
1477 |           },
1478 |           "webhook_url": { "type": ["string", "null"] }
1479 |         }
1480 |       },
1481 |       "RetryWebhookQuery": {
1482 |         "type": "object",
1483 |         "required": ["bot_uuid"],
1484 |         "properties": { "bot_uuid": { "type": "string" } }
1485 |       },
1486 |       "ScheduleOrigin": {
1487 |         "oneOf": [
1488 |           {
1489 |             "type": "object",
1490 |             "required": ["Event"],
1491 |             "properties": {
1492 |               "Event": {
1493 |                 "type": "object",
1494 |                 "required": ["id"],
1495 |                 "properties": { "id": { "type": "integer", "format": "int32" } }
1496 |               }
1497 |             },
1498 |             "additionalProperties": false
1499 |           },
1500 |           {
1501 |             "type": "object",
1502 |             "required": ["ScheduledBot"],
1503 |             "properties": {
1504 |               "ScheduledBot": {
1505 |                 "type": "object",
1506 |                 "required": ["id"],
1507 |                 "properties": { "id": { "type": "integer", "format": "int32" } }
1508 |               }
1509 |             },
1510 |             "additionalProperties": false
1511 |           }
1512 |         ]
1513 |       },
1514 |       "SpeechToText": {
1515 |         "anyOf": [
1516 |           { "$ref": "#/components/schemas/SpeechToTextApiParameter" },
1517 |           { "$ref": "#/components/schemas/SpeechToTextProvider" }
1518 |         ]
1519 |       },
1520 |       "SpeechToText2": {
1521 |         "type": "object",
1522 |         "required": ["provider"],
1523 |         "properties": {
1524 |           "api_key": { "type": ["string", "null"] },
1525 |           "provider": { "$ref": "#/components/schemas/SpeechToTextProvider" }
1526 |         }
1527 |       },
1528 |       "SpeechToTextApiParameter": {
1529 |         "type": "object",
1530 |         "required": ["provider"],
1531 |         "properties": {
1532 |           "api_key": { "type": ["string", "null"] },
1533 |           "provider": { "$ref": "#/components/schemas/SpeechToTextProvider" }
1534 |         }
1535 |       },
1536 |       "SpeechToTextProvider": { "type": "string", "enum": ["Gladia", "Runpod", "Default"] },
1537 |       "StartRecordFailedQuery": {
1538 |         "type": "object",
1539 |         "properties": { "bot_uuid": { "type": ["string", "null"] } }
1540 |       },
1541 |       "StreamingApiParameter": {
1542 |         "type": "object",
1543 |         "properties": {
1544 |           "audio_frequency": {
1545 |             "anyOf": [{ "$ref": "#/components/schemas/AudioFrequency" }, { "type": "null" }]
1546 |           },
1547 |           "input": { "type": ["string", "null"] },
1548 |           "output": { "type": ["string", "null"] }
1549 |         }
1550 |       },
1551 |       "SyncResponse": {
1552 |         "type": "object",
1553 |         "properties": {
1554 |           "affected_event_uuids": {
1555 |             "description": "UUIDs of affected events",
1556 |             "type": ["array", "null"],
1557 |             "items": { "type": "string", "format": "uuid" }
1558 |           },
1559 |           "has_updates": {
1560 |             "description": "timestamp of last updated event if some events has been updated.",
1561 |             "type": ["string", "null"],
1562 |             "format": "date-time"
1563 |           }
1564 |         }
1565 |       },
1566 |       "SystemTime": {
1567 |         "type": "object",
1568 |         "required": ["nanos_since_epoch", "secs_since_epoch"],
1569 |         "properties": {
1570 |           "nanos_since_epoch": { "type": "integer", "format": "uint32", "minimum": 0.0 },
1571 |           "secs_since_epoch": { "type": "integer", "format": "uint64", "minimum": 0.0 }
1572 |         }
1573 |       },
1574 |       "TokenConsumptionByService": {
1575 |         "type": "object",
1576 |         "required": [
1577 |           "duration",
1578 |           "recording_tokens",
1579 |           "streaming_input_hour",
1580 |           "streaming_input_tokens",
1581 |           "streaming_output_hour",
1582 |           "streaming_output_tokens",
1583 |           "transcription_byok_hour",
1584 |           "transcription_byok_tokens",
1585 |           "transcription_hour",
1586 |           "transcription_tokens"
1587 |         ],
1588 |         "properties": {
1589 |           "duration": { "type": "string" },
1590 |           "recording_tokens": { "type": "string" },
1591 |           "streaming_input_hour": { "type": "string" },
1592 |           "streaming_input_tokens": { "type": "string" },
1593 |           "streaming_output_hour": { "type": "string" },
1594 |           "streaming_output_tokens": { "type": "string" },
1595 |           "transcription_byok_hour": { "type": "string" },
1596 |           "transcription_byok_tokens": { "type": "string" },
1597 |           "transcription_hour": { "type": "string" },
1598 |           "transcription_tokens": { "type": "string" }
1599 |         }
1600 |       },
1601 |       "TokenConsumptionQuery": {
1602 |         "type": "object",
1603 |         "required": ["end_date", "start_date"],
1604 |         "properties": {
1605 |           "end_date": { "type": "string", "format": "partial-date-time" },
1606 |           "start_date": { "type": "string", "format": "partial-date-time" }
1607 |         }
1608 |       },
1609 |       "Transcript": {
1610 |         "type": "object",
1611 |         "required": ["bot_id", "id", "speaker", "start_time", "words"],
1612 |         "properties": {
1613 |           "bot_id": { "type": "integer", "format": "int32" },
1614 |           "end_time": { "type": ["number", "null"], "format": "double" },
1615 |           "id": { "type": "integer", "format": "int32" },
1616 |           "lang": { "type": ["string", "null"] },
1617 |           "speaker": { "type": "string" },
1618 |           "start_time": { "type": "number", "format": "double" },
1619 |           "user_id": { "type": ["integer", "null"], "format": "int32" },
1620 |           "words": { "type": "array", "items": { "$ref": "#/components/schemas/Word" } }
1621 |         }
1622 |       },
1623 |       "Transcript2": {
1624 |         "type": "object",
1625 |         "required": ["bot_id", "speaker", "start_time"],
1626 |         "properties": {
1627 |           "bot_id": { "type": "integer", "format": "int32" },
1628 |           "end_time": { "type": ["number", "null"], "format": "double" },
1629 |           "lang": { "type": ["string", "null"] },
1630 |           "speaker": { "type": "string" },
1631 |           "start_time": { "type": "number", "format": "double" },
1632 |           "user_id": { "type": ["integer", "null"], "format": "int32" }
1633 |         }
1634 |       },
1635 |       "Transcript3": {
1636 |         "type": "object",
1637 |         "required": ["bot_id", "id", "speaker", "start_time"],
1638 |         "properties": {
1639 |           "bot_id": { "type": "integer", "format": "int32" },
1640 |           "end_time": { "type": ["number", "null"], "format": "double" },
1641 |           "id": { "type": "integer", "format": "int32" },
1642 |           "lang": { "type": ["string", "null"] },
1643 |           "speaker": { "type": "string" },
1644 |           "start_time": { "type": "number", "format": "double" },
1645 |           "user_id": { "type": ["integer", "null"], "format": "int32" }
1646 |         }
1647 |       },
1648 |       "Transcript4": {
1649 |         "type": "object",
1650 |         "required": ["id"],
1651 |         "properties": {
1652 |           "bot_id": { "type": ["integer", "null"], "format": "int32" },
1653 |           "end_time": { "type": ["number", "null"], "format": "double" },
1654 |           "id": { "type": "integer", "format": "int32" },
1655 |           "lang": { "type": ["string", "null"] },
1656 |           "speaker": { "type": ["string", "null"] },
1657 |           "start_time": { "type": ["number", "null"], "format": "double" },
1658 |           "user_id": { "type": ["integer", "null"], "format": "int32" }
1659 |         }
1660 |       },
1661 |       "UpdateCalendarParams": {
1662 |         "type": "object",
1663 |         "required": ["oauth_client_id", "oauth_client_secret", "oauth_refresh_token", "platform"],
1664 |         "properties": {
1665 |           "oauth_client_id": { "type": "string" },
1666 |           "oauth_client_secret": { "type": "string" },
1667 |           "oauth_refresh_token": { "type": "string" },
1668 |           "platform": { "$ref": "#/components/schemas/Provider" }
1669 |         }
1670 |       },
1671 |       "UserTokensResponse": {
1672 |         "type": "object",
1673 |         "required": ["available_tokens", "total_tokens_purchased"],
1674 |         "properties": {
1675 |           "available_tokens": { "type": "string" },
1676 |           "last_purchase_date": { "type": ["string", "null"], "format": "partial-date-time" },
1677 |           "total_tokens_purchased": { "type": "string" }
1678 |         }
1679 |       },
1680 |       "Version": {
1681 |         "type": "object",
1682 |         "required": ["build_date", "location"],
1683 |         "properties": { "build_date": { "type": "string" }, "location": { "type": "string" } }
1684 |       },
1685 |       "Word": {
1686 |         "type": "object",
1687 |         "required": ["bot_id", "end_time", "id", "start_time", "text"],
1688 |         "properties": {
1689 |           "bot_id": { "type": "integer", "format": "int32" },
1690 |           "end_time": { "type": "number", "format": "double" },
1691 |           "id": { "type": "integer", "format": "int32" },
1692 |           "start_time": { "type": "number", "format": "double" },
1693 |           "text": { "type": "string" },
1694 |           "user_id": { "type": ["integer", "null"], "format": "int32" }
1695 |         }
1696 |       }
1697 |     }
1698 |   },
1699 |   "security": [{ "ApiKeyAuth": [] }, { "LegacyApiKeyAuth": [] }]
1700 | }
1701 | 
```
Page 2/2FirstPrevNextLast