---
name: microsoft-teams-oauth-app
description: >-
  Registers a Microsoft Entra ID application for Microsoft Teams access
  through Microsoft Graph. Builds on the `microsoft-entra-app-registration`
  base skill; read that first. This skill covers only what Teams adds: the
  Teams permission families and which ones a tenant admin will approve, the
  protected export APIs and the metering switched off in August 2025 (plus the
  licenses that still apply), resource-specific consent and what it cannot
  reach, application access policies for online meetings, change notifications
  with resource data, and Teams throttling. Use when asked to get Microsoft
  Teams OAuth credentials, connect Teams channels, chats, meetings, recordings
  or transcripts, export Teams messages, fix a Teams connector returning `403
  Forbidden` or `402 Payment Required`, or scope a Teams app for a security
  review. For SharePoint use the SharePoint skill, for mail and calendar the
  Outlook skill, and for files the OneDrive skill.
---

# Microsoft Teams (Microsoft Entra ID) OAuth2 App Registration

Get a working OAuth2 client for Microsoft Teams — an Entra ID app registration carrying the right Teams Graph
permissions — for a platform that connects many customers' tenants.

Registering the app is the base skill's job and is mechanical. Teams adds three things that decide whether the
connector ships: **which permissions a tenant admin will sign off on** (most of the useful ones are admin-restricted,
and the bulk-export ones are application-only, so there is no user-consent path at all); **what the protected export
APIs now cost**, which changed materially in 2025 and which most runbooks still get wrong; and **two narrowing
mechanisms** — resource-specific consent and application access policies — that are the only honest answers to
"why does your app need every message in our tenant?".

Read the protected-API section before you promise a customer anything about message export.

## Built on: `microsoft-entra-app-registration`

**Read the base skill first.** It is the source of truth for the Entra mechanics this one assumes:

- **Supported account types** — Teams lives in work/school tenants, so **Multiple Entra ID tenants** is the choice;
  personal-account audiences buy nothing and collapse the permission ceiling to 30. `AADSTS50194` when a
  single-tenant registration is called through `/common`.
- **Redirect URIs** — Web vs SPA vs public-client platform types (SPA cannot carry a client secret), exact
  case-sensitive matching, the count and length limits, and the Unified.to callback hosts.
- **Delegated vs application permissions, and admin consent** — who may consent to what, and that Microsoft Graph
  *application* permissions require a **Privileged Role Administrator**, not an Application Administrator. Every
  app-only Teams design walks into that one.
- **Client secrets and certificates** — the 24-month cap, the **Value** shown exactly once, `AADSTS7000222` on
  expiry, add-then-delete rotation, and the per-cloud authority and Graph hosts.
- **`offline_access`, publisher verification and the generic AADSTS symptom table** — no Teams scope carries a
  refresh token of its own; `offline_access` is what gets you one.

The base also holds the inputs to collect, the run order, tenant ownership, the credential hand-off and the generic
stop-and-ask conditions. **A reader who loads only this file is missing all of it.** Everything below is
Teams-specific.

## Extra inputs to collect

On top of the base skill's batch:

| Input | Notes |
| --- | --- |
| **Delegated (a user connects) or application (app-only background sync)?** | Decides the whole permission story — §2 |
| **Is bulk message export in scope, or per-conversation reads?** | Export is application-only and has its own licensing — §1 |
| **Meetings: recordings, transcripts, AI insights?** | Each is a separate permission; AI insights needs a Copilot license — §1, §4 |
| **Will a Teams app (manifest) be shipped, or only an Entra app?** | RSC needs a Teams app installed in each team/chat — §3 |
| **Who administers Teams on the customer side, and can they run PowerShell?** | Application access policies and RSC tenant settings are cmdlets — §3, §4 |
| **Change notifications: with resource data, or bare?** | Resource data means an encryption certificate you must manage — §5 |

## Platform state (verified 2026-09-20 — re-verify before trusting)

- **Teams APIs are no longer metered.** Microsoft: *"Starting August 25, 2025, the Teams APIs are no longer metered,
  and no billing configuration is required to use these APIs. If your application is configured for billing, no
  action is required."* The `model` query parameter *"is no longer required and is ignored when supplied"*, and the
  only API still listed as metered in Graph is SharePoint/OneDrive `assignSensitivityLabel`. The old payment-model
  article (model=A / model=B / evaluation mode, $0.00075 per message, $0.003 per recording minute, $0.0022 per
  transcript minute) is **marked deprecated and slated for removal in June 2026** but was still live on this date —
  treat every number in it as history, not as a quote you give a customer.
- **Two licensing exceptions survived the change.** Teams **meeting AI insights** APIs require a **Microsoft 365
  Copilot** license for the user, and the **DLP `policyViolation` PATCH** APIs require a license carrying the
  Microsoft Communications DLP service plan. Everything else on that list is now unmetered.
- **Export APIs need a Teams license, not a compliance license.** The current export article states that
  organizations *"must have an active Microsoft Teams license assigned to the users whose data is being exported"*
  and — explicitly — that use of the export APIs *"does not require a Microsoft Purview Data Loss Prevention (DLP)
  service plan or any additional DLP licensing."* That reverses years of guidance; older runbooks and vendor
  comparisons still demand E5.
- **The protected-API request form appears to be gone.** The dedicated protected-APIs page (`/graph/teams-protected-apis`)
  now returns **404** with no redirect, the historical `aka.ms/teamsgraph/requestaccess` short link no longer
  resolves to a form, and the export article's prerequisites now read: *"Microsoft Teams APIs in Microsoft Graph that
  access sensitive data are considered protected APIs. You can call these APIs as long as the requirements for
  accessing without a user are met."* No approval queue, no Wednesday review cycle. **But** the metered-APIs page
  still carries the stranded sentence *"Some metered APIs and services in Microsoft Graph are protected and require
  additional validation beyond permissions and admin consent. Before you can use these protected APIs, you must
  submit a request"* — while listing only a SharePoint API. Microsoft's own pages disagree. Plan for the permissive
  reading, and treat a `403` naming protected-API validation as the signal that the gate is back; do not tell a
  customer an approval is required (or that none is) without testing it in their tenant.
- **`ChannelMessage.Read.Group` and `ChatMessage.Read.Chat`** are the RSC forms of the broad message-read
  permissions and are real, documented alternatives — §3.
- **Application access policies for meetings are still mandatory** for app-only access to online meetings and
  virtual events. Permission plus admin consent is not enough; §4.

If Graph or the Teams admin surfaces do not behave like this, stop and report what you actually see.

## 1. Export and the protected APIs — the fact that decides the design

The bulk-export surface is the reason most Teams integrations exist and the reason most of them stall.

| API | Who can call it |
| --- | --- |
| `GET /teams/{team-id}/channels/getAllMessages` | **Application only.** Delegated is *"Not supported"* — for work/school *and* personal accounts |
| `GET /users/{user-id}/chats/getAllMessages`, `GET /me/chats/getAllMessages` | Application only |
| `GET /teams/{team-id}/channels/getAllRetainedMessages`, `GET /users/{user-id}/chats/getAllRetainedMessages` | Application only |
| `GET /teamwork/deletedTeams/{id}/channels/getAllMessages` | Application only |
| `GET /users/{id}/onlineMeetings/getAllRecordings` / `getAllTranscripts` | Application only, **plus** an application access policy (§4) |

The documented application permissions for the export set are `Chat.Read.All` (all 1:1, group and meeting chat
messages), `ChannelMessage.Read.All` (all channel messages), `User.Read.All`, `OnlineMeetingTranscript.Read.All` and
`OnlineMeetingRecording.Read.All`.

Consequences to say out loud before anyone designs around it:

- **There is no delegated export.** A "the user connects their own Teams account" flow can never call
  `getAllMessages`. It reads messages one conversation at a time through `/teams/{id}/channels/{id}/messages` and
  `/chats/{id}/messages`, at delegated rate limits, bounded by what that user can see. If the requirement is
  tenant-wide history, the requirement is an app-only app with a Privileged Role Administrator's consent.
- **Cost is no longer the objection; scope is.** Since 2025-08-25 there is no per-message charge and no Azure
  subscription to attach. What a customer's security review now weighs is `Chat.Read.All` + `ChannelMessage.Read.All`
  on an app-only app — every private chat in the tenant, forever, with no user in the loop. Expect that to be the
  hard conversation, and expect §3 to be the answer for anything short of compliance archiving.
- **Retention and deletion windows bound what export can return**: user-deleted messages for **21 days**, deleted
  teams/channels and deleted or inactive users for **30 days**, and retained (hold/retention-policy) messages through
  the separate `getAllRetainedMessages` calls.
- **`$top` is a hint, not a page size.** Microsoft: *"The $top value is a maximum hint and not a guaranteed page
  size… a response may return fewer items than requested and include an @odata.nextLink"* because messages come from
  several underlying mailboxes. The recommended ceiling for export is **250**. Pagination must follow
  `@odata.nextLink` until it is absent — never infer completion from a short page.
- **Historic `402 Payment Required` errors** mean an old metered path: no Azure subscription, `model=A` without the
  DLP license, or evaluation capacity exhausted. On a current tenant a fresh `402` on a Teams call is a signal that
  something in your stack is still sending `model=` or that you are calling AI insights without a Copilot license.

## 2. The Teams permission families

Teams permissions divide sharply by mode. This table is what an admin sees, and whether their consent is required
(from the Graph permissions reference, 2026-09-20):

| Permission | Delegated | Application | Admin consent |
| --- | --- | --- | --- |
| `Team.ReadBasic.All` | yes | yes | delegated **no**, application yes |
| `Channel.ReadBasic.All` | yes | yes | delegated **no**, application yes |
| `ChannelMessage.Read.All` | yes | yes | **yes, both** |
| `ChannelMessage.Send` | yes | — | no |
| `ChatMessage.Read` | yes | — | no |
| `Chat.Read` / `Chat.ReadBasic` | yes | — | no |
| `Chat.Read.All` | **— (application only)** | yes | yes |
| `TeamMember.Read.All` | yes | yes | **yes, both** |
| `OnlineMeetings.Read` | yes | — | no |
| `OnlineMeetings.Read.All` | **— (application only)** | yes | yes |
| `OnlineMeetingRecording.Read.All` / `OnlineMeetingTranscript.Read.All` | yes | yes | **yes, both** |
| `OnlineMeetingAiInsight.Read.All` | yes | yes | yes, both — **plus a Copilot license** |
| `CallRecordings.Read.All` / `CallTranscripts.Read.All` | yes | yes | **yes, both** |
| `CallRecords.Read.All` | **— (application only)** | yes | yes |
| `Chat.Read.WhereInstalled` | **— (application only)** | yes | yes |

Four traps live in that table:

1. **`Chat.Read.All` has no delegated form.** A delegated design that wants "all the user's chats" uses `Chat.Read`
   plus `ChatMessage.Read`, not `Chat.Read.All`; requesting the latter in a delegated authorize URL fails as an
   invalid scope, not as a consent prompt.
2. **The `ReadBasic` pair is the cheap entry.** `Team.ReadBasic.All` and `Channel.ReadBasic.All` are the only
   interesting Teams scopes an ordinary user can consent to delegated — names and descriptions of teams and channels,
   nothing inside them. A connector that only lists conversations for a picker should stop there and request message
   scopes later, at the point the customer chooses to sync.
3. **Delegated `ChannelMessage.Read.All` still needs an admin.** It reads only what the signed-in user can already
   see, so the *access* is narrow, but the consent is not user-grantable. Discovering that at a customer's consent
   screen (`AADSTS90094`) costs a scheduling round trip; say it up front.
4. **Any scope in the token is honored.** Requesting `Chat.Read` "for least privilege" alongside `Chat.Read.All`
   yields a tenant-wide app with a least-privilege label. Narrow means the broad scopes come out of the request.

Teams file attachments live in SharePoint and OneDrive, so `Files.Read.All` and `Sites.Read.All` ride along with any
message-read design that resolves attachments. They are delegated-user-consentable but tenant-wide in application
mode, and a security review will read them as SharePoint access — see the SharePoint skill for that argument.

### Product fact — Unified.to's Microsoft Teams connector, as of 2026-09-20

Recorded from the connector's own metadata. **Confirm the current set with the connector's owner before you register
anything**; scope lists change and this is a snapshot.

- **Delegated only.** It authorizes and exchanges tokens against the multitenant `/common` v2.0 endpoints, sends
  `prompt=select_account`, and ships Unified.to's own shared client credentials by default. There is **no app-only
  (client credentials) path**, which means no export APIs (§1), no application access policy (§4), and no
  application-mode change notifications (§5) as the connector stands today.
- **`offline_access` is present in every per-object scope set**, alongside `openid`, `email` and `profile`; the
  login-only flow requests just `openid`, `email`, `profile` and `User.Read`.
- **Messaging**: channels use `Team.ReadBasic.All` + `Chat.Read`; message read adds `Channel.ReadBasic.All`,
  `ChannelMessage.Read.All`, `ChatMessage.Read`, plus `Files.Read.All` and `Sites.Read.All` for attachments; message
  write uses `ChannelMessage.Send` and `ChatMessage.Send` (with `ChannelMessage.Read.All` retained).
- **Calendar and meetings**: `Calendars.Read` / `Calendars.ReadWrite`; webinars use `VirtualEvent.Read` /
  `VirtualEvent.ReadWrite` (delegated `VirtualEvent.Read` **is** admin-consent-required); recordings request
  `OnlineMeetingRecording.Read.All`, `OnlineMeetingTranscript.Read.All`, `OnlineMeetingAiInsight.Read.All` and
  `OnlineMeetings.Read`, and the unified-communications recording object adds `CallRecordings.Read.All` and
  `CallTranscripts.Read.All`.
- **Directory objects** reuse the shared Entra directory scopes — `Directory.Read.All`, `User.Read.All`,
  `Group.Read.All` and their `ReadWrite` forms — all admin-restricted in both modes.
- **Protected or metered APIs implied: none today.** Nothing in the set reaches an export endpoint, and nothing is
  metered after 2025-08-25. The one live licensing dependency is `OnlineMeetingAiInsight.Read.All`, which returns
  nothing for a user without a **Microsoft 365 Copilot** license — a per-user condition your support team will see as
  "recordings work, insights are empty".
- **There is a second, separate Teams connector** (`microsoftteamsbot`, added 2025-12) covering channels, messages
  and message events with a near-identical delegated scope set, intended for the bot-shaped Teams integration. Which
  connector a customer is on changes which scope set applies and which Entra app the credentials belong to. That
  connector's registration is a different task and deserves its own skill; do not assume this one's answers carry.

Expect the message-read set — `ChannelMessage.Read.All` in particular, and the directory scopes — to be escalated to
a tenant admin in any customer with a security review.

## 3. RSC — the narrow alternative, and what it cannot reach

Resource-specific consent lets a **team owner, chat member or meeting organizer** grant your app access to *that one*
team or chat, instead of a tenant admin granting access to all of them. It is the Teams analogue of SharePoint's
`Sites.Selected`, and it is the strongest answer you have to a security review.

**It requires shipping a Teams app**, not just an Entra app:

- Register the Entra app as usual, then declare the RSC permissions in the **Teams app manifest**, under
  `webApplicationInfo` (`id` = the Entra app ID; `resource` is unused by RSC but must be a non-empty string) and
  `authorization.permissions.resourceSpecific`, each entry a `name` plus `type` of `Application` or `Delegated`.
  Manifest **v1.12 or later** is required for delegated RSC. (Manifest v1.11 and earlier used a flat
  `applicationPermissions` array; Microsoft recommends moving off it.)
- **One Entra app per Teams app.** Microsoft: *"You mustn't share your Microsoft Entra app ID across multiple Teams
  apps… Installing multiple Teams apps associated with the same Microsoft Entra app ID will cause installation or
  runtime failures."*
- **Consent happens at install**, in the Teams client, by whoever installs the app in that team or chat — not in the
  Entra admin center, and not visible there. Application RSC grants are readable through
  `GET /teams/{id}/permissionGrants`, `/chats/{id}/permissionGrants` and `/users/{id}/permissionGrants`, matching
  `clientAppId` to the manifest's `webApplicationInfo.id`.
- **The tenant can switch it off.** Team and chat RSC are governed by tenant settings (`ManagedByMicrosoft` by
  default, or `EnabledForAllApps` / `DisabledForAllApps`, set through Graph PowerShell); user-scope RSC is governed
  by a separate Teams app-settings property. A customer with RSC disabled cannot install your app no matter what the
  manifest says.

What RSC reaches, and what it does not:

| Can | Cannot |
| --- | --- |
| `ChannelMessage.Read.Group` — this team's channel messages | Anything tenant-wide: `/teams/getAllMessages`, `/chats/getAllMessages` |
| `ChatMessage.Read.Chat` — this chat's messages (beta for subscriptions) | A team or chat where the app is not installed |
| `TeamMember.Read.Group`, `TeamSettings.*`, `Channel.*`, `TeamsTab.*` | Directory-wide reads (`User.Read.All`, `Group.Read.All`) |
| `OnlineMeetingRecording.Read.Chat`, `OnlineMeetingTranscript.Read.Chat`, `ChannelMeetingRecording.Read.Group` | Meetings not associated with an installed chat or team |
| `TeamsActivity.Send.*` — always tenant-enabled, always consented on install | A per-user identity: RSC calls are application calls |

Two things to flag before anyone designs around RSC:

- **RSC grants are not attributed to a user.** Microsoft's own warning: the app *"can be allowed to perform actions
  that the user can't, such as deleting a tab."* Permission checks you might expect from a delegated flow are not
  there.
- **Onboarding is per team and per chat, by the resource owner.** Your platform cannot self-serve it, the same way
  it cannot self-serve a `Sites.Selected` grant. Design the connect flow to say so.

## 4. Application access policies — meetings only, and not the Exchange one

For **application** permissions against online meetings and virtual events, permission plus admin consent is not
enough. Microsoft requires the tenant admin to also grant an **application access policy** naming your app's client
ID against the users whose meetings it may act for.

```powershell
New-CsApplicationAccessPolicy -Identity Test-policy -AppIds "<client-id>","<client-id-2>" -Description "…"
Grant-CsApplicationAccessPolicy -PolicyName Test-policy -Identity "<user-object-id>"
Grant-CsApplicationAccessPolicy -PolicyName Test-policy -Global   # optional: tenant default
```

- **It covers exactly these application permissions**: `OnlineMeetings.Read.All`, `OnlineMeetings.ReadWrite.All`,
  `OnlineMeetingArtifact.Read.All`, `OnlineMeetingTranscript.Read.All`, `OnlineMeetingRecording.Read.All` and
  `VirtualEvent.Read.All`. Nothing else in Graph is gated this way.
- **`-Identity` means two different things** — the policy name when creating, the *user's object ID* when granting.
- **Granting applies to online meetings and virtual events together.** If they must be managed separately, Microsoft
  says to use two applications.
- **Changes take up to 30 minutes** to show up in Graph calls. A "it still 403s" report inside that window is not a
  bug.
- Without it, the call fails `403 Forbidden` with `"No application access policy found for this app"` — a message
  that names neither the permission nor the user, and looks exactly like a missing consent.
- **This is not the Exchange `ApplicationAccessPolicy`** used to scope app-only *mail* access to a mail-enabled
  security group. Same words, different cmdlet, different product, different scoping model. Do not hand a customer
  instructions for one while debugging the other.

## 5. Change notifications, and what resource data costs you

Polling Teams is a policy violation, not just bad practice: Microsoft allows a resource to be polled **once per
day**, and says apps that ignore this *"will be considered in violation of the Microsoft APIs Terms of Use"*, which
*"may result in additional throttling or the suspension or termination of your use of the Microsoft APIs."*
Subscriptions are the supported design.

- **Tenant-wide message subscriptions are application-only**: `/teams/getAllMessages` needs
  `ChannelMessage.Read.All`, `/chats/getAllMessages` needs `Chat.Read.All`. Per-channel and per-chat subscriptions
  support delegated (`ChannelMessage.Read.All`, `Chat.Read`) and the RSC forms.
- **Lifetimes and renewal**: Teams `chatMessage`, `channel`, `chat`, `team`, `conversationMember`, `callRecording`
  and `callTranscript` subscriptions max out at **4,320 minutes (three days)**, so renewal is a permanent background
  job. And for chat/channel message subscriptions, an `expirationDateTime` more than **one hour** out **requires** a
  `lifecycleNotificationUrl` in the subscription request, or creation fails outright.
- **Quotas are shared and they bite**: one subscription per app *per channel or chat*, ten per user for
  user-wide chat subscriptions, and **10,000 total Teams subscriptions per organization across every Teams resource
  type combined** — chats, messages, channels, teams, members, recordings, transcripts. Past that, new Teams
  subscriptions fail `403 Forbidden`. A per-conversation subscription model does not scale to a large tenant; design
  for the tenant-wide resource or accept the cap.
- **Resource data means key management.** `includeResourceData: true` requires an `encryptionCertificate` (public key
  only, Base64 X.509, RSA, **2,048–4,096 bits**; self-signed is fine — Graph does not verify the issuer) and your own
  `encryptionCertificateId` (up to 128 characters) to match notifications to keys. Decryption is two-stage: unwrap
  the per-item `dataKey` with your private key using **RSA-OAEP**, verify `dataSignature` with **HMAC-SHA256**, then
  decrypt `data` with **AES-CBC/PKCS7** using the **first 16 bytes of the symmetric key as the IV**. Rotate by
  issuing new certificates at renewal and keeping the old private key until no notification references its ID.
- **Validate every notification.** Rich notifications carry `validationTokens`; the publisher claim (`azp` on v2.0
  tokens, `appid` on v1.0) must be `0bf30f3b-4a52-48df-9a82-234910c4a086`. Respond `202 Accepted` first, validate
  after. A `null` `validationTokens` means app misconfiguration: set the service principal's
  `appRoleAssignmentRequired` to `false`, or explicitly assign the *Microsoft Graph Change Tracking* service
  principal a role.
- Teams `chatMessage` notifications are documented as arriving in **under 10 seconds** typically. If your product
  promises real time, this is the mechanism; nothing else gets close.

## 6. Throttling — the four dimensions

Teams throttles on **per app** (summed across every tenant — the one multitenant connectors forget), **per app per
tenant**, **per resource** (one team, channel or chat) and **per user**. A request is throttled when it exceeds *any*
applicable limit, and a sustained rate of roughly **83 percent** of the listed value is evaluated over a longer
window, so a workload that runs continuously at the published number still gets throttled.

| Call | Per app | Per app per tenant | Per resource | Per user |
| --- | --- | --- | --- | --- |
| `GET /teams/{id}/channels/{id}/messages` | 200 rps | 20 rps | 1 rps per channel | — |
| `GET /chats/{id}/messages` | 200 rps | 20 rps | 1 rps per chat | — |
| `POST` channel messages or replies | 500 rps | 50 rps | 1 rps per channel | 1 rps |
| `GET /me/joinedTeams`, `/users/{id}/joinedTeams` | 300 rps | 30 rps | — | — |
| `GET /teams/{id}/channels` | 1200 rps | 60 rps | 4 rps per team | — |
| Export `getAllMessages` / `getAllRetainedMessages` | 1000 rps | 200 rps | — | — |
| Anything unlisted (GET) | 1500 rps | 30 rps | 1 rps per chat or channel | 1 rps |

**The per-resource limit is the one that hurts.** One request per second against a single channel or chat means a
backfill of a busy conversation is serialized no matter how much concurrency you have; parallelize across
conversations, not within one. Export endpoints exist precisely because they are exempt from the per-resource cap.
Honor `Retry-After`, back off exponentially, and do not register extra app IDs to dodge the per-app ceiling — the
per-tenant limits still apply and Microsoft treats it as abuse.

Teams' own product limits (members per team, channels per team, message size) apply identically through Graph; see
the Teams limits and specifications doc before designing around a number you assumed.

## 7. Verify the Teams-specific parts

Run the base skill's end-to-end check first (a second tenant, a `refresh_token` in the response, the token's `scp` or
`roles` claim inspected). Then:

1. **List before you read.** `GET /me/joinedTeams`, then that team's channels, then one channel's messages. Each step
   uses a different permission; a failure tells you which one never made it into the token.
2. **Test with a non-admin user.** Delegated `ChannelMessage.Read.All` and `TeamMember.Read.All` both need admin
   consent, and the admin's own session hides that.
3. **If meetings are in scope**, fetch one recording and one transcript for a meeting the connecting user organized —
   these are organizer-scoped, so testing with a participant proves nothing. If AI insights are in scope, confirm the
   test user holds a Copilot license before filing an empty result as a bug.
4. **If a subscription is in scope**, create it, let it deliver one notification, decrypt it, and then let it renew at
   least once. Three-day expiry means the renewal path is the part that breaks in production.

| Symptom | Cause |
| --- | --- |
| `403 Forbidden`, `"No application access policy found for this app"` | App-only meeting call with no application access policy, or granted under 30 minutes ago (§4) |
| Delegated authorize rejects a scope as invalid | Asking for an application-only permission (`Chat.Read.All`, `OnlineMeetings.Read.All`, `CallRecords.Read.All`) in a delegated request (§2) |
| `getAllMessages` returns 403 or is refused for a delegated token | Export is application-only; there is no delegated form (§1) |
| Export returns fewer items than `$top` | `$top` is a hint; follow `@odata.nextLink` to completion (§1) |
| Recordings work, AI insights come back empty | The user has no Microsoft 365 Copilot license (§1, §2) |
| `402 Payment Required` on a Teams call | A leftover `model=` parameter, or an AI-insight/DLP licensing gap — metering itself ended 2025-08-25 (Platform state) |
| Subscription creation fails naming `lifecycleNotificationUrl` | Expiry set beyond one hour on a chat/channel message subscription without it (§5) |
| New subscriptions fail `403` in a large tenant | The 10,000-per-organization Teams subscription cap, shared across all Teams resource types (§5) |
| `validationTokens` arrives `null` | `appRoleAssignmentRequired` is `true` without a role assigned to Microsoft Graph Change Tracking (§5) |
| RSC app fails to install: `WebApplicationInfoIdOfSideloadedAppMustBeInTheSameTenantAsUser` | The Entra registration is in a different tenant than the installer, and the installer is not a tenant admin (§3) |
| RSC permissions invisible in the Entra admin center | They live in the Teams app manifest and the install, not the Entra app; read them from `permissionGrants` (§3) |
| Steady 429s on one busy channel despite low overall volume | The 1 rps per-resource limit; parallelize across conversations, not within one (§6) |

## Hand off — the Teams additions

The base skill's hand-off list applies. Add to the closing summary: **delegated or application**, and for application
which meeting permissions are covered by an application access policy, who created it and against which users; the
exact Teams scope strings and which of them required admin consent; whether any export endpoint is in scope and what
licensing the customer was told about it (Teams license for exported users; Copilot for AI insights); if RSC is used,
the Teams app manifest's declared permissions and which teams or chats it is installed in; and for change
notifications, which resources are subscribed, the certificate identifier in use and who owns the renewal job.

## Stop and ask

On top of the base skill's conditions, hand back to a human when:

- The design needs tenant-wide message export and no Privileged Role Administrator is available to consent Graph
  application permissions — there is no delegated fallback and no lower permission that works (§1, §2).
- A customer's security review objects to `Chat.Read.All` or `ChannelMessage.Read.All` and RSC has not been evaluated.
  That is a product decision with an onboarding cost, not a config detail (§3).
- Anyone plans around per-message billing, `model=A`/`model=B`, evaluation-mode quotas or an Azure billing
  subscription for Teams APIs. Those ended 2025-08-25; the plan needs rewriting, not a workaround.
- A plan depends on a protected-API approval form, or on there definitely being no approval at all. Microsoft's pages
  disagree on this today — test in the customer's tenant rather than asserting either (Platform state).
- Someone proposes polling a Teams resource more than once a day instead of subscribing (§5).
- A shared Entra app ID is proposed across more than one Teams app, or an RSC design assumes a tenant-wide reach it
  does not have (§3).
- Compliance, legal hold, eDiscovery or Purview retention enters the conversation. Export APIs touch it; the
  obligations are the customer's counsel's call, not yours.
- The work is really about SharePoint sites, Outlook mail and calendar, OneDrive files or plain sign-in — those are
  different skills with different permission models.

## References

The base skill carries the shared Entra references. These are the Teams-specific ones; verified 2026-09-20, every
link returned HTTP 200.

- Use the Microsoft Graph API to work with Microsoft Teams (limits, polling requirements) — https://learn.microsoft.com/en-us/graph/api/resources/teams-api-overview
- Microsoft Teams API overview (concepts) — https://learn.microsoft.com/en-us/graph/teams-concept-overview
- Export content with the Microsoft Teams Export APIs — https://learn.microsoft.com/en-us/microsoftteams/export-teams-content
- Metered APIs and services in Microsoft Graph (Teams no longer metered) — https://learn.microsoft.com/en-us/graph/metered-api-list
- Payment models and licensing for Teams APIs (deprecated; historical model=A/B) — https://learn.microsoft.com/en-us/graph/teams-licenses
- channel: getAllMessages — https://learn.microsoft.com/en-us/graph/api/channel-getallmessages
- chats: getAllMessages — https://learn.microsoft.com/en-us/graph/api/chats-getallmessages
- chat: getAllRetainedMessages — https://learn.microsoft.com/en-us/graph/api/chat-getallretainedmessages
- List channel messages — https://learn.microsoft.com/en-us/graph/api/channel-list-messages
- List joined teams — https://learn.microsoft.com/en-us/graph/api/user-list-joinedteams
- Resource-specific consent for apps — https://learn.microsoft.com/en-us/microsoftteams/platform/graph-api/rsc/resource-specific-consent
- Grant RSC permissions to an app (manifest, tenant controls) — https://learn.microsoft.com/en-us/microsoftteams/platform/graph-api/rsc/grant-resource-specific-consent
- Configure an application access policy (cloud communications) — https://learn.microsoft.com/en-us/graph/cloud-communication-online-meeting-application-access-policy
- New-CsApplicationAccessPolicy — https://learn.microsoft.com/en-us/powershell/module/microsoftteams/new-csapplicationaccesspolicy
- Grant-CsApplicationAccessPolicy — https://learn.microsoft.com/en-us/powershell/module/microsoftteams/grant-csapplicationaccesspolicy
- Change notifications for messages in Teams channels and chats — https://learn.microsoft.com/en-us/graph/teams-changenotifications-chatmessage
- Change notifications for Microsoft Teams resources (overview) — https://learn.microsoft.com/en-us/graph/teams-change-notification-in-microsoft-teams-overview
- Change notifications with resource data (encryption, validation) — https://learn.microsoft.com/en-us/graph/change-notifications-with-resource-data
- Change notifications overview (lifetimes, per-resource quotas) — https://learn.microsoft.com/en-us/graph/change-notifications-overview
- Reduce missing subscriptions and change notifications (lifecycle events) — https://learn.microsoft.com/en-us/graph/change-notifications-lifecycle-events
- Change notifications for call records — https://learn.microsoft.com/en-us/graph/changenotifications-for-callrecords
- callRecording resource type — https://learn.microsoft.com/en-us/graph/api/resources/callrecording
- callTranscript resource type — https://learn.microsoft.com/en-us/graph/api/resources/calltranscript
- Use Graph APIs to fetch meeting transcripts — https://learn.microsoft.com/en-us/microsoftteams/platform/graph-api/meeting-transcripts/api-transcripts
- Meeting AI insights (Copilot license) — https://learn.microsoft.com/en-us/microsoftteams/platform/graph-api/meeting-transcripts/meeting-insights
- Microsoft Graph throttling limits (Microsoft Teams service limits) — https://learn.microsoft.com/en-us/graph/throttling-limits
- Limits and specifications for Microsoft Teams — https://learn.microsoft.com/en-us/microsoftteams/limits-specifications-teams
