Microsoft Teams runs most enterprise meetings, and those meetings sit behind organization policies that decide who joins, when, and whether a bot is allowed in at all. Native Teams transcription depends on a tenant admin enabling it and on each license tier, so it isn’t something you can switch on from your own app. Notetaker sends a bot to the meeting join URL instead, which means you control recording and transcription from your own code regardless of the tenant’s transcription settings.
This recipe sends a Notetaker bot into a Teams meeting, then pulls back the transcript and recording once the call ends.
Send a Notetaker bot to a Teams meeting
Section titled “Send a Notetaker bot to a Teams meeting”To record a Teams meeting, make a POST /v3/grants/{grant_id}/notetakers request with the Teams join URL in meeting_link. Notetaker joins as a participant, and you enable what you want back through meeting_settings. Teams meetings support all four bot states, so set transcription to true to get a speaker-labelled transcript.
The meeting_link is the full teams.microsoft.com/l/meetup-join URL from the meeting invite. Set join_time as a Unix timestamp for a scheduled call, or omit it to join a meeting that’s already running. The leave_after_silence_seconds field accepts a value between 10 and 3600 seconds, so the bot leaves on its own once the room goes quiet.
curl --request POST \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/notetakers" \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json' \ --data '{ "join_time": 1732657774, "meeting_link": "https://meet.google.com/xyz-abcd-ijk", "meeting_settings": { "action_items": true, "action_items_settings": { "custom_instructions": "Only return the 5 most important action items." }, "audio_recording": true, "leave_after_silence_seconds": 360, "summary": true, "summary_settings": { "custom_instructions": "Return this summary in the MEDPIC sales methodology." }, "transcription": true, "transcription_settings": { "expected_languages": ["en", "es"], "fallback_language": "en" }, "video_recording": true }, "name": "Nylas Notetaker" }'{ "request_id": "5fa64c92-e840-4357-86b9-2aa364d35b88", "data": { "id": "<NOTETAKER_ID>", "name": "Nyla's Notetaker", "join_time": 1732657774, "meeting_link": "<MEETING_URL>", "meeting_provider": "Google Meet", "state": "scheduled", "meeting_settings": { "action_items": true, "action_items_settings": { "custom_instructions": "Only return the 5 most important action items." }, "audio_recording": true, "summary": true, "summary_settings": { "custom_instructions": "Return this summary in the MEDPIC sales methodology." }, "transcription": true, "transcription_settings": { "expected_languages": ["en", "es"], "fallback_language": "en" }, "video_recording": true } }}import Nylas from "nylas";
const nylas = new Nylas({ apiKey: "<NYLAS_API_KEY>", apiUri: "<NYLAS_API_URI>",});
async function inviteNotetaker() { try { const notetaker = await nylas.notetakers.create({ identifier: "<NYLAS_GRANT_ID>", requestBody: { meetingLink: "https://meet.google.com/abc-defg-hij", name: "Nylas Notetaker", meetingSettings: { videoRecording: true, audioRecording: true, transcription: true, }, }, });
console.log("Notetaker:", notetaker); } catch (error) { console.error("Error inviting notetaker:", error); }}
inviteNotetaker();from nylas import Client
nylas = Client( "<NYLAS_API_KEY>", "<NYLAS_API_URI>",)
notetaker = nylas.notetakers.invite( request_body={ "meeting_link": "https://meet.google.com/abc-defg-hij", "name": "Nylas Notetaker", "meeting_settings": { "video_recording": True, "audio_recording": True, "transcription": True, }, }, identifier="<NYLAS_GRANT_ID>",)
print("Invited notetaker:", notetaker)The response returns the Notetaker id in a scheduled state. Store that ID, because every later call to read media, check history, or remove the bot needs it. The same request body works for Google Meet and Zoom, so a single integration covers all three providers without per-provider branches.
Get the transcript and recording
Section titled “Get the transcript and recording”Once the bot leaves the Teams call, fetch the output with GET /v3/grants/{grant_id}/notetakers/{notetaker_id}/media. The response returns a recording object (type: "video/mp4" with duration and size), a transcript object (type: "application/json"), and the summary and action_items files if you enabled them. Processing takes a few minutes after the meeting ends.
Each download URL in the response carries a ttl of 3600, so the file stays available for 3,600 seconds before it’s removed. Download the transcript and recording to your own storage inside that window. If the URLs expire, call the same endpoint again to mint fresh ones.
curl --request GET \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/notetakers/<NOTETAKER_ID>/media" \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'{ "request_id": "5fa64c92-e840-4357-86b9-2aa364d35b88", "data": { "action_items": { "created_at": 1703088000, "expires_at": 1704297600, "name": "meeting_action_items.json", "size": 289, "ttl": 3600, "type": "application/json", "url": "https://storage.googleapis.com/nylas-notetaker-uc1-prod-notetaker/..." }, "recording": { "created_at": 1703088000, "duration": 1800, "expires_at": 1704297600, "name": "meeting_recording.mp4", "size": 52428800, "ttl": 3600, "type": "video/mp4", "url": "https://storage.googleapis.com/nylas-notetaker-uc1-prod-notetaker/..." }, "summary": { "created_at": 1703088000, "expires_at": 1704297600, "name": "meeting_summary.json", "size": 437, "ttl": 3600, "type": "application/json", "url": "https://storage.googleapis.com/nylas-notetaker-uc1-prod-notetaker/..." }, "thumbnail": { "created_at": 1703088000, "expires_at": 1704297600, "name": "thumbnail.png", "size": 437, "ttl": 3600, "type": "image/png", "url": "https://storage.googleapis.com/nylas-notetaker-uc1-prod-notetaker/..." }, "transcript": { "created_at": 1703088000, "expires_at": 1704297600, "name": "transcript.json", "size": 10240, "ttl": 3600, "type": "application/json", "url": "https://storage.googleapis.com/nylas-notetaker-uc1-prod-notetaker/..." } }}For the full media model, including the speaker-labelled transcript format and retention rules, see Handling Notetaker media files.
Transcribe automatically with webhooks
Section titled “Transcribe automatically with webhooks”Polling the media endpoint wastes calls, since processing can take several minutes. Subscribe to the notetaker.media webhook instead, which fires once the recording and transcript are ready, so your server reacts the moment a file lands. A single subscription covers every Teams meeting your app records.
For the full webhook setup, see Automate transcription with Notetaker webhooks.
Things to know about Teams meetings
Section titled “Things to know about Teams meetings”Teams behaves differently from Meet and Zoom in a few ways that affect whether the bot gets in and what it captures. These details come from the tenant’s configuration, not from Nylas, so check them with the meeting organizer when a join fails.
Get the join URL from a calendar event
Section titled “Get the join URL from a calendar event”The meeting_link must be the https://teams.microsoft.com/l/meetup-join/... URL, not the Outlook event link or a tenant vanity domain. When you sync calendars, that URL lives in the event’s online meeting details. If you store events through Nylas, pull the conferencing link from the event object rather than asking users to paste it. Roughly every Teams invite carries one join URL per meeting, so one event maps to one bot.
The lobby and external-join policies can hold the bot
Section titled “The lobby and external-join policies can hold the bot”Notetaker joins as a non-signed-in guest, so a meeting that admits external participants through the lobby parks the bot there until someone lets it in. If nobody admits it within 10 minutes of the scheduled join_time, the request times out and Nylas sends a notetaker.meeting_state webhook with status set to failed_entry. For recurring recordings, ask the organizer to set the lobby so that people in the organization bypass it, which lets a participant admit the bot quickly.
Org-level Teams meeting policies can block bots
Section titled “Org-level Teams meeting policies can block bots”Teams admins control meeting behavior through policies in the Teams admin center, and some of those settings stop anonymous or app participants from joining. If the bot consistently fails entry across every meeting in one tenant, a policy is the likely cause, not the join URL. Point the admin to Manage meeting policies in Microsoft Teams to confirm anonymous users can join.
Recording captures the main meeting area
Section titled “Recording captures the main meeting area”For Teams, the video recording covers the main meeting area at 1280x720 and 12 FPS, the same specs Notetaker uses across providers. The transcript is generated from the meeting audio with speaker labels, independent of whether the Teams tenant has native transcription turned on. You get a consistent transcript even on tenants where Microsoft transcription is disabled by policy.
What’s next
Section titled “What’s next”- Send a Notetaker bot with the API for the full request body and bot lifecycle
- Download the transcript and recording for the media retrieval flow in detail
- Transcribe a Zoom meeting for the same workflow on Zoom
- Using Nylas Notetaker for settings, language hints, and silence detection