Best Practices for v2 API
@meeting-baas/sdk - v2 API Reference / v2-best-practices
Best Practices for v2 API
This guide covers recommended patterns and best practices for using the Meeting BaaS v2 API effectively.
Client Configuration
Use Environment Variables
import { createBaasClient } from "@meeting-baas/sdk";
const client = createBaasClient({
api_key: process.env.MEETING_BAAS_API_KEY!,
api_version: "v2",
timeout: 60000 // 60 seconds for long-running operations
});Singleton Pattern
Create a single client instance and reuse it:
// lib/meeting-baas.ts
import { createBaasClient } from "@meeting-baas/sdk";
let clientInstance: ReturnType<typeof createBaasClient> | null = null;
export function getMeetingBaasClient() {
if (!clientInstance) {
clientInstance = createBaasClient({
api_key: process.env.MEETING_BAAS_API_KEY!,
api_version: "v2"
});
}
return clientInstance;
}Error Handling
Structured Error Handling
async function createBotSafely(meetingUrl: string, botName: string) {
const result = await client.createBot({
meeting_url: meetingUrl,
bot_name: botName
});
if (!result.success) {
// Log full error for debugging
console.error("Failed to create bot:", {
error: result.error,
code: result.code,
statusCode: result.statusCode,
details: result.details
});
// Return user-friendly message
throw new Error(getErrorMessage(result.code));
}
return result.data;
}
function getErrorMessage(code: string): string {
const messages: Record<string, string> = {
INVALID_MEETING_URL: "Please provide a valid meeting URL",
RATE_LIMIT_EXCEEDED: "Too many requests. Please try again later",
UNAUTHORIZED: "Invalid API key or insufficient permissions",
BOT_ALREADY_IN_MEETING: "A bot is already in this meeting"
};
return messages[code] || "An unexpected error occurred";
}Retry with Exponential Backoff
async function withRetry\<T\>(
operation: () => Promise\<T\>,
maxRetries = 3,
baseDelay = 1000
): Promise\<T\> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error: any) {
const isLastAttempt = attempt === maxRetries;
const shouldRetry = error.code === "RATE_LIMIT_EXCEEDED" ||
error.statusCode >= 500;
if (!shouldRetry || isLastAttempt) {
throw error;
}
const delay = baseDelay * Math.pow(2, attempt - 1);
console.log(`Retry attempt ${attempt}/${maxRetries} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error("Max retries exceeded");
}
// Usage
const bot = await withRetry(() =>
client.createBot({
meeting_url: "https://meet.google.com/abc-def-ghi",
bot_name: "Assistant"
})
);Bot Management
Poll for Completion
async function waitForBotCompletion(
botId: string,
maxWaitMs = 3600000, // 1 hour
pollIntervalMs = 5000 // 5 seconds
): Promise<V2.GetBotResponse> {
const startTime = Date.now();
while (Date.now() - startTime < maxWaitMs) {
const result = await client.getBot({ bot_id: botId });
if (!result.success) {
throw new Error(`Failed to get bot status: ${result.error}`);
}
const status = result.data.status;
if (status === "completed" || status === "failed") {
return result.data;
}
await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
}
throw new Error("Bot did not complete within timeout period");
}Resource Cleanup
Always clean up resources:
async function processMeeting(meetingUrl: string) {
let botId: string | null = null;
try {
// Create bot
const createResult = await client.createBot({
meeting_url: meetingUrl,
bot_name: "Meeting Processor"
});
if (!createResult.success) {
throw new Error(createResult.error);
}
botId = createResult.data.bot_id;
// Wait for completion
const bot = await waitForBotCompletion(botId);
// Process data
if (bot.mp4_url) {
await downloadRecording(bot.mp4_url);
}
return { success: true, botId };
} catch (error) {
console.error("Error processing meeting:", error);
return { success: false, error };
} finally {
// Clean up bot data if needed
if (botId) {
await client.deleteBotData({ bot_id: botId }).catch(err => {
console.error("Failed to delete bot data:", err);
});
}
}
}Batch Operations
Process in Chunks
async function createBotsInChunks(
meetings: Array<{ url: string; name: string }>,
chunkSize = 10
) {
const chunks = [];
for (let i = 0; i < meetings.length; i += chunkSize) {
chunks.push(meetings.slice(i, i + chunkSize));
}
const results = [];
for (const chunk of chunks) {
const batchResult = await client.batchCreateBots({
bots: chunk.map(m => ({
meeting_url: m.url,
bot_name: m.name
}))
});
if (batchResult.success) {
results.push(...batchResult.data);
if (batchResult.errors && batchResult.errors.length > 0) {
console.error(`Failed to create ${batchResult.errors.length} bots in chunk`);
batchResult.errors.forEach(err => {
console.error(` - ${err.error} (${err.code})`);
});
}
}
// Rate limiting: wait between chunks
await new Promise(resolve => setTimeout(resolve, 1000));
}
return results;
}TypeScript Best Practices
Use Type Guards
import type { V2 } from "@meeting-baas/sdk";
function isBotCompleted(bot: V2.GetBotResponse): bot is V2.GetBotResponse & { status: "completed" } {
return bot.status === "completed";
}
async function processBotIfCompleted(botId: string) {
const result = await client.getBot({ bot_id: botId });
if (!result.success) {
return;
}
if (isBotCompleted(result.data)) {
// TypeScript knows status is "completed" here
console.log("Processing completed bot:", result.data.bot_id);
if (result.data.mp4_url) {
await downloadRecording(result.data.mp4_url);
}
}
}Leverage Discriminated Unions
async function handleBotOperation() {
const result = await client.createBot({
meeting_url: "https://meet.google.com/abc",
bot_name: "Test"
});
// Type narrowing with success field
if (result.success) {
// TypeScript knows result.data exists
console.log("Bot ID:", result.data.bot_id);
} else {
// TypeScript knows result.error, code, statusCode exist
console.error("Error:", result.error);
console.error("Code:", result.code);
}
}Calendar Integration
Sync Patterns
async function syncCalendarEvents(calendarId: string) {
const now = new Date();
const nextWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
const result = await client.listCalendarEvents({
calendar_id: calendarId,
start_date_gte: now.toISOString(),
start_date_lte: nextWeek.toISOString()
});
if (!result.success) {
console.error("Failed to list events:", result.error);
return [];
}
return result.data.events;
}
// Schedule regular syncs
setInterval(async () => {
const calendars = await client.listCalendars();
if (calendars.success) {
for (const calendar of calendars.data.calendars) {
await syncCalendarEvents(calendar.uuid);
}
}
}, 5 * 60 * 1000); // Every 5 minutesSecurity
Validate Input
import { z } from "zod";
const meetingUrlSchema = z.string().url().refine(
(url) => {
const validDomains = [
'meet.google.com',
'zoom.us',
'teams.microsoft.com'
];
try {
const hostname = new URL(url).hostname;
return validDomains.some(domain => hostname.includes(domain));
} catch {
return false;
}
},
{ message: "Invalid meeting platform URL" }
);
async function createBotWithValidation(meetingUrl: string, botName: string) {
// Validate input
const urlValidation = meetingUrlSchema.safeParse(meetingUrl);
if (!urlValidation.success) {
throw new Error(`Invalid meeting URL: ${urlValidation.error.message}`);
}
// Proceed with API call
return await client.createBot({
meeting_url: meetingUrl,
bot_name: botName
});
}Never Log Sensitive Data
function safeLog(operation: string, data: any) {
const sanitized = { ...data };
// Remove sensitive fields
delete sanitized.api_key;
delete sanitized.oauth_client_secret;
delete sanitized.oauth_refresh_token;
console.log(`[${operation}]`, sanitized);
}
// Usage
safeLog("Creating bot", { meeting_url, bot_name });Testing
Mock API Responses
import { vi } from "vitest";
// Mock successful response
const mockClient = {
createBot: vi.fn().mockResolvedValue({
success: true,
data: {
bot_id: "test-bot-id",
status: "joining",
meeting_url: "https://meet.google.com/abc"
}
})
};
// Test your code
test("creates bot successfully", async () => {
const result = await mockClient.createBot({
meeting_url: "https://meet.google.com/abc",
bot_name: "Test Bot"
});
expect(result.success).toBe(true);
expect(result.data.bot_id).toBe("test-bot-id");
});Performance
Parallel Requests
async function getBotDetails(botIds: string[]) {
// Execute requests in parallel
const results = await Promise.allSettled(
botIds.map(id => client.getBot({ bot_id: id }))
);
const bots = results
.filter((r): r is PromiseFulfilledResult<any> =>
r.status === "fulfilled" && r.value.success
)
.map(r => r.value.data);
return bots;
}Caching
const botCache = new Map<string, { data: any; timestamp: number }>();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
async function getCachedBot(botId: string) {
const cached = botCache.get(botId);
const now = Date.now();
if (cached && now - cached.timestamp < CACHE_TTL) {
return cached.data;
}
const result = await client.getBot({ bot_id: botId });
if (result.success) {
botCache.set(botId, {
data: result.data,
timestamp: now
});
return result.data;
}
return null;
}