---
name: microsoft-teams-meeting-bot-app
description: Sets up and registers the Microsoft Entra app behind Unified.to's Microsoft Teams (meeting bot) integration (`microsoftteamsmeetingbot`) end to end — your own multitenant app registration (Unified has no shared credentials), the Web redirect URI, the delegated Graph permissions for meetings, recordings and transcripts (the recording, transcript and AI-insight ones need admin consent), the `CallRecords.Read.All` application permission with tenant admin consent in every customer tenant, entering the client ID and secret in Unified, and an end-to-end test from `READY` to `CALL_ENDED` and `DONE` after the meeting. Use when asked to set up Teams meeting bots or an AI notetaker on Teams, register an Entra app for Unified's Teams meeting bot, grant CallRecords admin consent, or fix a Teams bot that stays `READY` after the meeting or never gets recordings. Read `microsoft-entra-app-registration` and `microsoft-teams-oauth-app` for the shared Entra and Teams Graph mechanics, and `unified-meeting-bots` for creating bots and handling events.
---

# Microsoft Teams (meeting bot) App Setup and Registration

Get a working **Microsoft Teams (meeting bot)** integration (`microsoftteamsmeetingbot`): your own Entra app with the
delegated permissions your users sign in with **and** the `CallRecords.Read.All` application permission, with a tenant
admin's consent where Graph requires it, entered on the integration in Unified.

Three things cost more than they look:

- **Bring your own Entra app.** The integration has no Unified OAuth2 credentials
  (`has_unified_oauth2_credentials: false`). It is separate from **Microsoft Teams** and **Microsoft Teams (bot)**;
  existing connections don't change.
- **Admin consent in every customer tenant.** Call records need the **application** permission
  `CallRecords.Read.All`, which can't be requested in a user sign-in: Unified gets an app-only token for it with your
  client ID and secret against the user's tenant. And three of the delegated permissions the bot uses —
  `OnlineMeetingRecording.Read.All`, `OnlineMeetingTranscript.Read.All` and `OnlineMeetingAiInsight.Read.All` — also
  require admin consent, so users can't approve them alone. So the **same** app needs a tenant admin's consent **in
  each customer tenant**. Without `CallRecords.Read.All` the `communications/callRecords` subscription fails, so
  creating the `calendar_botevent` webhook fails (all or nothing).
- **Nothing arrives during the meeting.** The bot never joins and captures no media; Microsoft Graph reports the
  meeting afterwards (call records ~30 minutes on average, up to 150; recordings and transcripts up to 240).

## Built on

- **`microsoft-entra-app-registration`** — supported account types, redirect URIs, delegated vs application
  permissions, admin consent, client secrets and expiry, publisher verification. Read first.
- **`microsoft-teams-oauth-app`** — Teams Graph permission families and what tenant admins approve.
- **`unified-meeting-bots`** — creating bots, the `calendar_botevent` webhook, statuses and the `501` matrix.

## Inputs to collect before you start

Ask in one batch. Never invent.

| Input | Notes |
| --- | --- |
| **Who owns the Entra app registration** | Your tenant; needs rights to register apps and create secrets |
| **Multitenant app** | Unified signs in through `/common`, so the app must allow **Multiple Entra ID tenants** (§1) |
| **Unified region** | Decides the redirect URI (§1) |
| **A tenant admin in each customer tenant** | A Privileged Role Administrator or Global Administrator, to consent to `CallRecords.Read.All` and the admin-only delegated permissions (§2) |
| **Is Graph access to transcripts allowed?** | A tenant admin can switch it off; transcript subscriptions then fail with `403 GraphAccessToTranscriptsDisabled` |
| **Which Unified permissions** | Bot permissions; `calendar_event_read`, `calendar_recording_read` if needed (§3) |
| **Are connected users the meeting organizers?** | Recordings and transcripts attach only for meetings they organize |
| **Client-secret expiry and rotation owner** | Entra secrets expire (`microsoft-entra-app-registration`) |

## Quick Start

1. Register the Entra app (multi-tenant), add the Web redirect URI, create a client secret (§1).
2. Add the delegated Graph permissions and the `CallRecords.Read.All` application permission (§2).
3. Grant admin consent in your tenant; plan admin consent in every customer tenant (§2).
4. In Unified, activate **Microsoft Teams (meeting bot)**, enter client ID and secret, select permissions (§3).
5. Nothing to configure in Azure or Teams for webhooks — create the `calendar_botevent` webhook before the meeting
   starts (§4).
6. Connect a work or school account and test end to end (§5, §6).

## Product state (from the TICKET-3338 Teams meeting-bot PR — re-verify before trusting)

- Integration type `microsoftteamsmeetingbot`, a post-meeting Graph monitor; not launched yet.
- `has_unified_oauth2_credentials: false` — your own Entra app is required.
- Inherits `calendar_calendar`, `calendar_event`, `calendar_busy`, `calendar_webinar`, `calendar_recording`,
  `uc_call` and `uc_recording` (`calendar_event` and `uc_call` have native webhooks).
- Delegated connections only; app-only (client credentials) connections aren't supported.
- `join_at` and any `recording_config` (even `{}`) return `501`; an update writes only `name` and silently ignores
  `meeting_url` and `event_id`.
- Without the Entra app on the integration, creating the `calendar_botevent` webhook returns `400`
  (`Microsoft Teams (meeting bot) needs your own Entra app: …`).

## 1. Register the Entra app

1. Microsoft Entra admin center → **Entra ID → App registrations → New registration**. Supported account types:
   **Multiple Entra ID tenants**. Unified signs in through the `/common` endpoint, so a single-tenant app fails for
   every user with `AADSTS50194`.
2. **Web** redirect URI for your region:
   - US `https://api.unified.to/oauth/code`
   - EU `https://api-eu.unified.to/oauth/code`
   - AU `https://api-au.unified.to/oauth/code`
3. **Certificates & secrets** → new client secret; copy its **Value** (shown once; not the secret ID) and the
   **Application (client) ID**. Secrets expire after at most 24 months.

## 2. Microsoft Graph permissions

### Delegated

| Permission | Unified permission | Used for | Admin consent |
| --- | --- | --- | --- |
| `User.Read`, `openid`, `email`, `profile`, `offline_access` | all | Sign-in and token refresh | No |
| `OnlineMeetings.Read` | `calendar_bot_read`, `calendar_bot_write`, `calendar_botevent_read` | Find the meeting by its join link | No |
| `Calendars.Read` | `calendar_bot_write`, `calendar_event_read` | Create a bot from `event_id`; list events | No |
| `OnlineMeetingRecording.Read.All` | `calendar_botevent_read`, `calendar_recording_read` | Recording notifications and reads | **Yes** |
| `OnlineMeetingTranscript.Read.All` | `calendar_botevent_read`, `calendar_recording_read` | Transcript notifications and reads | **Yes** |
| `OnlineMeetingAiInsight.Read.All` | `calendar_recording_read` | Reading the `calendar_recording` a bot points to | **Yes** |

So a user can't connect with `calendar_botevent_read` or `calendar_recording_read` until a tenant admin has consented.

`calendar_recording_read` also requests `OnlineMeetings.Read`. Creating a bot from an `event_id` needs only
`calendar_bot_write` (which carries `Calendars.Read`); `calendar_event_read` is for listing events.

For other inherited permissions (calendars, busy, webinars, calls), add the delegated permissions the Microsoft Teams
integration uses for them.

### Application

| Permission | Used for |
| --- | --- |
| `CallRecords.Read.All` | The call-record subscription and reads. **Needs admin consent.** |

Select **Grant admin consent** in your own tenant, and have **each customer's tenant admin** consent to the app in
theirs (for example through the admin consent URL for your client ID — see `microsoft-entra-app-registration`). That
consent covers `CallRecords.Read.All` and the three admin-only delegated permissions above. Granting a Microsoft Graph
**application** permission takes a Privileged Role Administrator (or Global Administrator); Cloud Application and
Application Administrators can't grant Graph application permissions.

## 3. Enter the credentials in Unified

1. `app.unified.to` → **Integrations** → activate **Microsoft Teams (meeting bot)**.
2. Enter the Entra app's **client ID** and **client secret**.
3. Select permissions: `calendar_bot_read`, `calendar_bot_write`, `calendar_botevent_read`, plus
   `calendar_event_read` and `calendar_recording_read` as needed. Unified requests the matching delegated
   permissions from §2.

## 4. Webhooks (nothing to configure in Azure or Teams)

When you create a native `calendar_botevent` webhook on a connection, Unified creates three Graph subscriptions and
renews them automatically. Unified gives each a 58-minute expiry, under Graph's 1-hour limit for Teams subscriptions
without a lifecycle notification URL:

1. `communications/callRecords` (app-only token) — the call record
2. the connected user's online meeting recordings (delegated)
3. the connected user's online meeting transcripts (delegated)

Creation is all or nothing: if the call-records subscription fails (usually missing admin consent for
`CallRecords.Read.All`), the webhook isn't created.

Create the webhook **before the meeting starts**: Graph sends a transcript notification only if the subscription
existed before transcription started. Create the bot before the meeting ends: only `READY` bots created before the
call record's end time are matched.

## 5. How end users connect

End users authorize **Microsoft Teams (meeting bot)** through the Authorization component or authorization link with
a work or school account. Their tenant admin must already have consented to `CallRecords.Read.All` and the
admin-only delegated permissions (§2); otherwise sign-in stops with an admin-consent prompt (`AADSTS90094`). The bot looks up the meeting as the connected user, so the meeting must be visible to them.

## 6. Test end to end

1. Create the native `calendar_botevent` / `created` webhook on the connection.
2. Create a `calendar_bot` with the Teams join link (`https://teams.microsoft.com/...`) or an `event_id` whose event
   has a Teams join link → `READY`.
3. Hold the meeting **organized by the connected user**, with recording and transcription on.
4. After it ends, expect `BOT_STATUS_CHANGE` → `CALL_ENDED` and `PARTICIPANT_JOINED` / `_LEFT` (times from the call
   record), then `RECORDING_READY`, `TRANSCRIPT_RECEIVED` per transcript line, and `DONE`. Allow up to 150 minutes for
   the call record and 240 for recordings and transcripts.
5. `GET /calendar/{connection_id}/recording/{recording_id}` on the same connection.

## Limits

- `501`: `join_at` and any `recording_config` (create and update). `400`: not a Teams join link, or an event without
  a Teams join link. `404`: unknown meeting or event, or one the user can't see. `409`: another unfinished bot for
  the exact same link.
- After the meeting only; updates write only `name` (`meeting_url` and `event_id` are ignored); no event on delete.
- Recordings and transcripts only for scheduled meetings the connected user organizes; others get the call record
  only. Graph's recording and transcript subscriptions don't support private-channel meetings.
- If a tenant admin has turned off Graph access to transcripts, the transcript subscription fails with
  `403 GraphAccessToTranscriptsDisabled`.
- Large meetings: only the first page of call-record participants (130) and sessions (60) is read.
- Several bot webhooks on one connection: call-record events go to whichever is processed first; recording and
  transcript events go to every webhook.
- Recurring meetings: recordings/transcripts attach only when created between the bot's `created_at` and `ended_at`.
- A meeting with no recording stays `CALL_ENDED`.

## Troubleshooting by error message

| Symptom | Cause and fix |
| --- | --- |
| Creating a bot returns `400` | Not a `https://teams.microsoft.com/...` link; event has no Teams join link |
| Creating a bot returns `404` | The meeting or event doesn't exist, or the connected user can't see it |
| Creating the webhook returns `400` `… needs your own Entra app` | Client ID / secret missing on the integration (§3) |
| Creating the webhook fails on the call-records subscription | No tenant admin consent to `CallRecords.Read.All` in that tenant (§2) |
| Creating a bot returns `501` | Request has `join_at` or `recording_config` — remove them |
| Bot stays `READY` after the meeting | Bot or webhook created after the meeting ended; call record not delivered yet (≤150 min) |
| No recording or transcript events | Connected user didn't organize the meeting; recording/transcription off; private-channel meeting; not delivered yet (≤240 min) |
| No transcript events, recordings arrive | Webhook created after transcription started (§4) |
| `403 GraphAccessToTranscriptsDisabled` | The tenant admin turned off Graph access to transcripts |
| `AADSTS65001` / consent required at sign-in | Delegated permission not consented (`microsoft-entra-app-registration`) |
| `AADSTS90094` admin consent required | The tenant admin hasn't consented to the admin-only permissions (§2) |
| `AADSTS7000215` invalid client secret | Wrong secret — use the secret's **Value**, not its ID, and update it in Unified |
| `AADSTS7000222` client secret expired | Create a new secret in Entra and update it in Unified |
| `AADSTS50194` not configured as a multitenant application | App is single-tenant — set Supported account types to **Multiple Entra ID tenants** (§1) |

## Stop and ask

- A customer's tenant admin won't consent to `CallRecords.Read.All` or the admin-only delegated permissions — the bot
  can't work there.
- The product needs live data during Teams meetings — not possible with this integration.
- Users who connect aren't the organizers, but the product needs recordings.

## References

- Microsoft Graph call records: https://learn.microsoft.com/graph/api/resources/callrecords-api-overview
- Graph change notifications: https://learn.microsoft.com/graph/change-notifications-overview
- Grant tenant-wide admin consent: https://learn.microsoft.com/entra/identity/enterprise-apps/grant-admin-consent
- Graph permissions reference (admin consent per permission): https://learn.microsoft.com/graph/permissions-reference
- Subscription limits and lifecycle notifications: https://learn.microsoft.com/graph/api/resources/subscription
- Recording and transcript change notifications: https://learn.microsoft.com/graph/teams-changenotifications-callrecording-and-calltranscript
- Entra error codes: https://learn.microsoft.com/entra/identity-platform/reference-error-codes
- Register an app: https://learn.microsoft.com/entra/identity-platform/quickstart-register-app
- `calendar_bot` model: https://docs.unified.to/calendar/bot/model
- Guide — How to Set Up a Microsoft Teams Meeting Bot with Unified (published at launch):
  https://docs.unified.to/guides/how_to_set_up_a_microsoft_teams_meeting_bot_with_unified
