# Transcribe a Microsoft Teams meeting

Source: https://developer.nylas.com/docs/cookbook/notetaker/transcribe-teams-meeting/

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

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.

```bash
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"
  }'

```

```json
{
  "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
    }
  }
}


```

```js [sendTeamsBot-Node.js SDK]

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();


```

```python [sendTeamsBot-Python SDK]

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

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.

```bash
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>'

```

```json
{
  "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](/docs/v3/notetaker/media-handling/).

## 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](/docs/cookbook/notetaker/notetaker-webhooks/).

## 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

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](/docs/v3/calendar/using-the-events-api/) 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

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

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](https://learn.microsoft.com/en-us/microsoftteams/meeting-policies-overview) to confirm anonymous users can join.

### 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

- [Send a Notetaker bot with the API](/docs/cookbook/notetaker/notetaker-api-guide/) for the full request body and bot lifecycle
- [Download the transcript and recording](/docs/cookbook/notetaker/get-transcript-and-recording/) for the media retrieval flow in detail
- [Transcribe a Zoom meeting](/docs/cookbook/notetaker/transcribe-zoom-meeting/) for the same workflow on Zoom
- [Using Nylas Notetaker](/docs/v3/notetaker/) for settings, language hints, and silence detection