# firstcall.dev > Meeting bot API for developers and AI agents. > Join Google Meet, Microsoft Teams, and Zoom meetings programmatically. > Real-time transcription, audio streaming, screenshots, and meeting participation. ## Pricing - Bot compute: $0.35/hour - Transcription (optional): $0.12/hour - Free tier: 360 minutes (6 hours), 1 concurrent bot - Pay-as-you-go with credits. No subscriptions. ## Authentication All API requests require a Bearer token: ``` Authorization: Bearer ak_live_xxxxxxxxxxxxxxxx ``` Get your API key at https://app.firstcall.dev/dashboard/api-keys ## API Base URL https://api.firstcall.dev ## Endpoints ### Create a bot POST /v1/bots Content-Type: application/json Request: ```json { "meet_url": "https://meet.google.com/abc-def-ghi", "bot_name": "My Bot", "mode": "audio-ws", "transcription": true } ``` Required fields: - meet_url (string): Google Meet, Teams, or Zoom URL (must be https) - bot_name (string): Display name in the meeting - mode (string): "audio-ws" | "webpage-audio" | "webpage-av" | "webpage-av-screenshare" Optional fields: - transcription (boolean): Enable real-time STT (default: false, costs $0.12/hr extra) - audio_streaming (boolean): Stream raw PCM audio to WebSocket (default: false) - webhook_url (string): Override account webhook for this bot's events Response (201): ```json { "bot_id": "bot-abc123def456", "status": "created", "ws_url": "wss://api.firstcall.dev/v1/bots/bot-abc123def456/ws", "created_at": "2026-03-21T14:30:01.123Z" } ``` ### List bots GET /v1/bots Response (200): ```json { "bots": [ { "bot_id": "bot-abc123", "meet_url": "https://meet.google.com/abc-def-ghi", "bot_name": "My Bot", "mode": "audio-ws", "platform": "google-meet", "status": "ready", "transcription": true, "created_at": "2026-03-21T14:30:01.123Z" } ] } ``` ### Get bot status GET /v1/bots/{bot_id} ### Stop a bot DELETE /v1/bots/{bot_id} Response (200): ```json { "bot_id": "bot-abc123", "status": "ending", "message": "Bot is leaving the meeting" } ``` ## WebSocket — Real-Time Meeting Data Connect after creating a bot to receive live events: ``` wss://api.firstcall.dev/v1/bots/{bot_id}/ws?api_key={your_api_key} ``` ### Events you receive transcript.final — Completed utterance: ```json { "type": "transcript.final", "text": "The quarterly numbers look great.", "speaker": { "id": "p-1", "name": "Alice" }, "is_final": true, "timestamp": "..." } ``` transcript.partial — In-progress transcription: ```json { "type": "transcript.partial", "text": "The quarterly", "speaker": { "id": "p-1", "name": "Alice" }, "is_final": false, "timestamp": "..." } ``` meeting.participant_joined: ```json { "type": "meeting.participant_joined", "participant": { "id": "p-1", "name": "Alice" }, "participants": [...], "timestamp": "..." } ``` meeting.participant_left: ```json { "type": "meeting.participant_left", "participant": { "id": "p-2", "name": "Bob" }, "participants": [...], "timestamp": "..." } ``` meeting.active_speaker: ```json { "type": "meeting.active_speaker", "speaker": { "id": "p-1", "name": "Alice" }, "timestamp": "..." } ``` meeting.chat_message: ```json { "type": "meeting.chat_message", "sender": "Alice", "message": "Can everyone hear me?", "timestamp": "..." } ``` audio.chunk (requires audio_streaming: true): ```json { "type": "audio.chunk", "data": "base64-pcm-s16le-16khz-mono", "timestamp": "..." } ``` screenshot.result (after screenshot.take command): ```json { "type": "screenshot.result", "data": "base64-jpeg", "width": 1920, "height": 1080, "request_id": "req-1", "timestamp": "..." } ``` ### Commands you send Send audio into meeting: ```json { "type": "audio.send", "data": "base64-pcm-s16le-16khz-mono" } ``` Send chat message: ```json { "type": "meeting.send_chat", "message": "Hello everyone!" } ``` Raise hand: ```json { "type": "meeting.raise_hand" } ``` Leave meeting: ```json { "type": "meeting.leave" } ``` Take screenshot: ```json { "type": "screenshot.take", "request_id": "req-1" } ``` Start periodic capture: ```json { "type": "capture.start", "interval_ms": 1000 } ``` Stop capture: ```json { "type": "capture.stop" } ``` Start screenshare (mode webpage-av-screenshare only): ```json { "type": "screenshare.start", "url": "https://your-app.com/slides" } ``` Stop screenshare: ```json { "type": "screenshare.stop" } ``` ## Webhooks — Bot Lifecycle Events Set your webhook URL at https://app.firstcall.dev/dashboard/api-keys Events delivered to your webhook: - bot.created — Bot was created - bot.status_changed — Bot changed status (starting, joining, waiting_room, initializing, ready, error, ended) - bot.ended — Final event with duration and cost breakdown Webhook signature header: X-FirstCall-Signature (HMAC-SHA256) Retry policy: 3 retries with exponential backoff (1s, 2s, 4s) ## Bot Status Flow created → starting → joining → waiting_room* → initializing → ready → ended (* waiting_room only if meeting has one) End reasons: left (you stopped it), ended (meeting ended), rejected, blocked, error, timeout_silence, timeout_alone, timeout_waiting_room ## Bot Modes - audio-ws: Audio capture + injection. Lightest. For transcription bots, AI voice agents. - webpage-audio: Webpage with audio routing into meeting. - webpage-av: Webpage with audio + video injection. For slides, dashboards. - webpage-av-screenshare: Full AV + screenshare. Custom bot branding via webpage. ## Supported Platforms - Google Meet (https://meet.google.com/...) - Microsoft Teams (https://teams.microsoft.com/... or https://teams.live.com/...) - Zoom (https://zoom.us/j/...) ## Rate Limits - REST API: 100 requests/second per API key - WebSocket: 100 commands/second per connection - WebSocket payload: 1 MB max ## Error Codes - 400: Bad request (missing/invalid fields) - 401: Invalid API key - 402: Insufficient credits - 404: Bot not found - 429: Rate limit or concurrent bot limit exceeded - 500: Internal error ## Complete Node.js Example ```javascript const WebSocket = require('ws'); // 1. Create bot const res = await fetch('https://api.firstcall.dev/v1/bots', { method: 'POST', headers: { 'Authorization': 'Bearer ak_live_xxxxxxxx', 'Content-Type': 'application/json', }, body: JSON.stringify({ meet_url: 'https://meet.google.com/abc-def-ghi', bot_name: 'AI Assistant', mode: 'audio-ws', transcription: true, }), }); const { bot_id, ws_url } = await res.json(); // 2. Connect WebSocket const ws = new WebSocket(`${ws_url}?api_key=ak_live_xxxxxxxx`); ws.on('message', (data) => { const event = JSON.parse(data); if (event.type === 'transcript.final') { console.log(`[${event.speaker?.name}] ${event.text}`); } }); // 3. Stop when done await fetch(`https://api.firstcall.dev/v1/bots/${bot_id}`, { method: 'DELETE', headers: { 'Authorization': 'Bearer ak_live_xxxxxxxx' }, }); ``` ## Complete Python Example ```python import requests import asyncio import json import websockets API_KEY = "ak_live_xxxxxxxx" # 1. Create bot r = requests.post("https://api.firstcall.dev/v1/bots", headers={"Authorization": f"Bearer {API_KEY}"}, json={"meet_url": "https://meet.google.com/abc-def-ghi", "bot_name": "AI Bot", "mode": "audio-ws", "transcription": True}) bot = r.json() # 2. Listen for events async def listen(): async with websockets.connect(f"{bot['ws_url']}?api_key={API_KEY}") as ws: async for msg in ws: event = json.loads(msg) if event["type"] == "transcript.final": print(f"[{event['speaker']['name']}] {event['text']}") asyncio.run(listen()) # 3. Stop requests.delete(f"https://api.firstcall.dev/v1/bots/{bot['bot_id']}", headers={"Authorization": f"Bearer {API_KEY}"}) ``` ## Full Documentation - REST API: https://firstcall.dev/docs/rest-api.md - WebSocket API: https://firstcall.dev/docs/websocket-api.md - Webhook API: https://firstcall.dev/docs/webhook-api.md - Dashboard: https://app.firstcall.dev