Building a bot that joins a Microsoft Teams meeting and captures what people say is not as simple as calling an API.
Microsoft does not provide a public API that lets an arbitrary bot join a live meeting in real time. The Graph API can fetch a transcript after a meeting ends, but that requires specific Microsoft 365 admin policies to be enabled, and transcript availability is not immediate.
The practical approach is browser automation. You script a real browser to open the Teams web client, navigate to the meeting, join as a participant, enable captions, and read what appears in the captions container. That is exactly what this guide covers, using Node.js and Playwright.
Quick Recap
Create a dedicated Microsoft account for the bot
Install Playwright and set up a Node.js project
Automate the Microsoft login flow and save the session using storageState
Modify the Teams meeting URL with ?launchAgent=join_only&type=meetup-join to bypass the app-picker dialog
Fill in the display name and click the join button on the pre-join screen
Enable captions through the More menu using data-tid selectors
Poll the captions container every two seconds and write new lines to a file
Use saved session cookies on repeat runs to avoid repeated login flows
How to Build a Microsoft Teams Meeting Bot
What You Need Before You Start?
Node.js version 18 or above
A dedicated Microsoft account for the bot (do not use a personal or work account)
A Microsoft Teams meeting link for testing
Basic knowledge of JavaScript and async/await
Use a fresh Microsoft account created only for this bot. Repeatedly automating login flows on a real account risks triggering Microsoft's bot detection or locking the account.
Step 1: Set Up Your Project
Create a project folder and initialise it.
bash
mkdir teams-bot
cd teams-bot
npm init -y
Install Playwright and its browser dependencies. Playwright works better than Puppeteer for Teams specifically because it handles permission prompts and browser arguments more cleanly out of the box.
bash
npminstall playwright
npx playwright install chromium
Create your main file.
bash
touchbot.js
Step 2: Launch the Browser
Start a Chromium browser with the flags needed to fake microphone and camera access. Without these flags, Teams shows a permissions prompt that the bot cannot click through automatically.
javascript
const{ chromium } = require('playwright');
asyncfunctionlaunchBrowser(){
const browser = await chromium.launch({
headless: false,// Keep visible while developing
args: [
'--use-fake-ui-for-media-stream',
'--use-fake-device-for-media-stream',
'--no-sandbox',
'--disable-setuid-sandbox'
]
});
const context = await browser.newContext({
permissions: ['microphone','camera']
});
const page = await context.newPage();
return{ browser, page };
}
Step 3: Sign In to Microsoft
The bot needs to be signed in to a Microsoft account before Teams will let it join a meeting. Automate the Microsoft login flow.
await page.click('#idBtn_Back');// Click "No" to keep session simple
}catch(e){
// Prompt did not appear, continue
}
console.log('Signed in successfully.');
}
Run this with headless: false the first time. Microsoft sometimes asks for additional verification on new logins. Handle that manually once, then save session cookies so you do not have to log in again on every run.
javascript
// After signing in successfully, save the session
If you navigate to that URL directly, Teams shows a dialog that says "Open in app or browser." Playwright cannot interact with that dialog because it is handled by the operating system. You bypass it by adding a specific query parameter to force the browser join flow directly.
javascript
asyncfunctionjoinMeeting(page, meetingUrl){
// Append ?launchAgent=join_only to skip the app-picker dialog
If the meeting has a lobby, a host will need to admit the bot before it enters the main call. After admission, the call-controls element becomes visible and the bot proceeds.
Step 5: Enable Live Captions
Captions are how the bot reads what people say during the meeting. You enable them through the meeting controls menu.
One important note: captions in Teams are buried deep in nested DOM elements and update dynamically. The selectors above are correct as of early 2025, but Microsoft updates the Teams web client regularly. If a selector stops working, open Teams in a regular Chrome window, inspect the captions container, and find the current data-tid attribute.
Step 6: Capture and Save the Transcript
Poll the captions container every two seconds and write new lines to a file.
The meeting link opens an app-picker dialog. Add ?launchAgent=join_only&type=meetup-join to the URL as shown in Step 4. This forces the browser join path.
The bot gets stuck in the lobby. Your bot appears as a participant with the display name you set. A meeting host needs to admit it manually unless the meeting settings allow everyone in. For testing, create a meeting with the lobby disabled.
The captions selector stops working. Microsoft ships Teams web client updates regularly. Open Teams in a normal browser window, enable captions, right-click the caption text, and click Inspect. Find the nearest parent element with a data-tid attribute and update your selector.
Caption text drops words. Teams captions paraphrase and truncate aggressively. They are not a verbatim transcription. For higher accuracy, route the meeting audio to an ASR service like Whisper or Deepgram instead of reading captions from the DOM.
Conclusion
A single bot running in headless Chromium works well for prototypes and internal tools. When you start running many bots concurrently, each one is a full browser process consuming significant CPU and memory.
You will also need to handle bot scheduling, retries when joins fail, monitoring for crashes mid-call, and keeping up with Teams UI updates that break your selectors. These are real ongoing maintenance costs.
If you need meeting bots in production without that overhead, Meetstream.ai handles all of it for you. It provides a clean API to send bots to Teams meetings, delivers real-time speaker-attributed transcripts, and stores recordings without you maintaining any browser infrastructure.