Microsoft Teams Error Guide: 'Forbidden' Missing RSC Permission for Teams App Channel Access
Fix Graph API 403 Forbidden errors caused by missing RSC permissions in Teams apps: diagnose manifest gaps, consent failures, and channel-scope access issues.
- #microsoft-teams
- #troubleshooting
- #errors
- #rsc
Overview
Resource-Specific Consent (RSC) is the permission model that lets a Teams app or bot access data scoped to a specific team or chat — without requiring a tenant-wide admin consent grant. When RSC permissions are missing, misconfigured, or the app was installed before a manifest update, Microsoft Graph returns HTTP 403 Forbidden on endpoints that require them.
The canonical Graph error body:
{
"error": {
"code": "Forbidden",
"message": "Required RSC permission not granted. The application is missing 'ChannelMessage.Read.Group' permission.",
"innerError": {
"code": "MissingRscPermission",
"date": "2026-06-23T11:02:44",
"request-id": "c3d4e5f6-a7b8-9012-cdef-234567890123",
"client-request-id": "c3d4e5f6-a7b8-9012-cdef-234567890123"
}
}
}
RSC permissions are declared in the Teams app manifest under authorization.permissions.resourceSpecific and are consented to at install time by a team owner — not by a tenant admin. They authorize access to a single team or chat. Unlike delegated or application permissions registered in Entra ID, RSC grants are local to the team and must be re-consented whenever the manifest changes or the app is reinstalled.
The 403 appears on any Graph call targeting /teams/{id}/channels, /teams/{id}/channels/{channelId}/messages, /chats/{chatId}/members, or similar team-scoped endpoints when the calling app’s RSC grant does not include the required permission.
Symptoms
- Bot or app returns 403 when reading channel messages even though the app is installed in the team.
- Calls succeed in delegated (user) context but fail when the app uses its own token (application context with RSC).
- The app worked previously but 403 started after a manifest version update was published.
- Team owners report the app is installed but it cannot post to specific channels.
- Proactive messaging to a channel fails while posting to General (the default channel) succeeds.
curl -s -X GET \
"https://graph.microsoft.com/v1.0/teams/3f9a1c2d-0000-4b5e-8f1a-aabbccddeeff/channels/19:abc123@thread.tacv2/messages" \
-H "Authorization: Bearer $TOKEN" | jq .
{
"error": {
"code": "Forbidden",
"message": "Required RSC permission not granted. The application is missing 'ChannelMessage.Read.Group' permission.",
"innerError": {
"code": "MissingRscPermission",
"date": "2026-06-23T11:02:44",
"request-id": "c3d4e5f6-a7b8-9012-cdef-234567890123"
}
}
}
Common Root Causes
1. RSC permission missing from the app manifest
The permission required by the API call is not declared in authorization.permissions.resourceSpecific in the manifest. Graph enforces only what the manifest declares — an undeclared permission is never granted, regardless of what is registered in Entra ID.
# Inspect the manifest of the installed app in the team
curl -s -X GET \
"https://graph.microsoft.com/v1.0/teams/3f9a1c2d-0000-4b5e-8f1a-aabbccddeeff/installedApps?$expand=teamsApp,teamsAppDefinition" \
-H "Authorization: Bearer $TOKEN" | \
jq '.value[] | select(.teamsApp.displayName=="MyTeamsBot") | .teamsAppDefinition | {version, publishingState}'
{
"version": "1.0.2",
"publishingState": "submitted"
}
Retrieve the manifest for that version from the Teams Admin Center or app package and check:
# Extract and inspect RSC block from a local manifest zip
unzip -p myteamsbot.zip manifest.json | jq '.authorization.permissions.resourceSpecific'
[
{ "name": "ChannelSettings.Read.Group", "type": "Application" },
{ "name": "TeamMember.Read.Group", "type": "Application" }
]
ChannelMessage.Read.Group is absent — the app cannot read channel messages even after reinstall.
2. The app was not reinstalled after a manifest update
RSC grants are captured at install time. If you added new RSC permissions to the manifest and published an update, the team’s existing installation still holds only the original consent. The app must be removed and reinstalled (or the team owner must explicitly update the installation) to trigger re-consent.
# Check the installed version vs. latest published version
curl -s -X GET \
"https://graph.microsoft.com/v1.0/teams/3f9a1c2d-0000-4b5e-8f1a-aabbccddeeff/installedApps?$expand=teamsApp,teamsAppDefinition" \
-H "Authorization: Bearer $TOKEN" | \
jq '.value[] | select(.teamsApp.displayName=="MyTeamsBot") | {installedVersion: .teamsAppDefinition.version}'
{
"installedVersion": "1.0.1"
}
If the catalog shows version 1.0.2 with the new RSC permissions but the team has 1.0.1 installed, the grant is stale. Upgrade the installation via Graph:
# Upgrade to the latest version (triggers re-consent prompt to team owner)
curl -s -X POST \
"https://graph.microsoft.com/v1.0/teams/3f9a1c2d-0000-4b5e-8f1a-aabbccddeeff/installedApps/$INSTALL_ID/upgrade" \
-H "Authorization: Bearer $TOKEN" | jq .
3. App is using application permissions instead of RSC (or vice versa)
RSC permissions grant access in application context — the app authenticates as itself, not on behalf of a user. If the app acquires a delegated token (user context), RSC does not apply and the user’s own team membership determines access. Conversely, an app expecting tenant-wide application permissions (e.g., ChannelMessage.Read.All) but installed with only RSC will fail if those tenant-wide permissions were not separately consented.
# Decode the token being used to identify its type
echo "$TOKEN" | cut -d'.' -f2 | base64 -d 2>/dev/null | \
jq '{token_type: (if .scp then "delegated" else "application" end), scp, roles, oid, tid}'
{
"token_type": "application",
"scp": null,
"roles": [],
"oid": "22222222-dddd-eeee-ffff-111111111111",
"tid": "11111111-aaaa-bbbb-cccc-000000000000"
}
An application token with empty roles and no scp claims is relying entirely on RSC. If RSC was not granted, there are no effective permissions.
4. RSC permission is team-scoped but the call targets a chat (or vice versa)
RSC permissions are namespaced: .Group permissions apply to teams, .Chat permissions apply to chats. Using ChannelMessage.Read.Group does not authorize /chats/{chatId}/messages, which requires ChatMessage.Read.Chat. Calling across scopes returns 403.
# Verify the chat-specific RSC permissions the app holds in this chat
curl -s -X GET \
"https://graph.microsoft.com/v1.0/chats/19:uid1_uid2@unq.gbl.spaces/installedApps?$expand=teamsApp,teamsAppDefinition" \
-H "Authorization: Bearer $TOKEN" | \
jq '.value[] | select(.teamsApp.displayName=="MyTeamsBot") | .teamsAppDefinition.version'
"1.0.2"
Then inspect the manifest for .Chat permissions:
unzip -p myteamsbot.zip manifest.json | jq '[.authorization.permissions.resourceSpecific[] | select(.name | endswith(".Chat"))]'
[]
No .Chat-scoped permissions — the app can only access team channels, not chats.
5. The app was installed in the wrong team or by the wrong owner
RSC grants are per-team-installation. An app installed in Team A cannot access Team B’s channels even if both teams use the same app. The calling app must be installed specifically in the team whose channels it is targeting.
# Verify the app is actually installed in the target team
curl -s -X GET \
"https://graph.microsoft.com/v1.0/teams/3f9a1c2d-0000-4b5e-8f1a-aabbccddeeff/installedApps?$expand=teamsApp" \
-H "Authorization: Bearer $TOKEN" | \
jq '[.value[] | {appId: .teamsApp.externalId, name: .teamsApp.displayName}]'
[
{ "appId": "00000000-1111-2222-3333-444444444444", "name": "SomeOtherBot" }
]
MyTeamsBot is not in the list — it was never installed in this particular team. The app must be added by a team owner before its RSC permissions take effect.
6. Manifest webApplicationInfo or authorization block is malformed
The Teams app validation pipeline may silently accept a manifest with a malformed authorization block. If the JSON structure is wrong — wrong key name (permissions vs resourceSpecific), wrong type value ("Application" vs "Delegated"), or a typo in the permission name — the RSC grant is either not created or created with zero effective permissions.
# Validate manifest structure against known-good RSC schema
unzip -p myteamsbot.zip manifest.json | jq '
.authorization.permissions.resourceSpecific[] |
select((.name | test("^[A-Za-z]+\\.(Read|Write|ReadWrite)\\.(Group|Chat)$") | not) or
(.type != "Application" and .type != "Delegated"))
'
{
"name": "ChannelMessage.ReadGroup",
"type": "application"
}
Two errors: missing dot before Group (should be ChannelMessage.Read.Group) and type is lowercase "application" instead of "Application". Teams silently drops this permission.
Diagnostic Workflow
Step 1: Identify the missing permission from the error message
The 403 error’s message field names the exact permission that is missing. Note it — e.g., ChannelMessage.Read.Group — then check whether it appears in the installed app’s manifest for the target team.
curl -s -X GET \
"https://graph.microsoft.com/v1.0/teams/$TEAM_ID/installedApps?$expand=teamsApp,teamsAppDefinition" \
-H "Authorization: Bearer $TOKEN" | \
jq '.value[] | select(.teamsApp.displayName=="MyTeamsBot")'
Step 2: Inspect the manifest’s RSC block
Extract the app package and check authorization.permissions.resourceSpecific:
unzip -p myteamsbot.zip manifest.json | \
jq '.authorization.permissions.resourceSpecific | map(select(.type=="Application"))'
If the required permission is absent, add it and republish. If it is present but malformed, fix the name/type casing.
Step 3: Check the installed version versus the published version
# Installed in team
curl -s "https://graph.microsoft.com/v1.0/teams/$TEAM_ID/installedApps?$expand=teamsAppDefinition" \
-H "Authorization: Bearer $TOKEN" | jq '.value[] | select(.teamsApp.displayName=="MyTeamsBot") | .teamsAppDefinition.version'
# Latest in tenant app catalog
curl -s "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps?$filter=externalId eq '$APP_EXTERNAL_ID'&$expand=appDefinitions" \
-H "Authorization: Bearer $TOKEN" | jq '.value[].appDefinitions | last | .version'
If versions differ, trigger an upgrade to re-consent the new RSC grants.
Step 4: Verify the app is installed in the correct team
curl -s "https://graph.microsoft.com/v1.0/teams/$TEAM_ID/installedApps?$expand=teamsApp" \
-H "Authorization: Bearer $TOKEN" | \
jq '[.value[].teamsApp.displayName]'
If the app is missing, install it:
curl -s -X POST \
"https://graph.microsoft.com/v1.0/teams/$TEAM_ID/installedApps" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"teamsApp@odata.bind\": \"https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/$TEAMS_APP_CATALOG_ID\"}" | jq .
Step 5: Force reinstall to re-trigger consent
# Remove and reinstall to re-consent updated RSC permissions
INSTALL_ID=$(curl -s "https://graph.microsoft.com/v1.0/teams/$TEAM_ID/installedApps?$expand=teamsApp" \
-H "Authorization: Bearer $TOKEN" | \
jq -r '.value[] | select(.teamsApp.displayName=="MyTeamsBot") | .id')
curl -s -X DELETE \
"https://graph.microsoft.com/v1.0/teams/$TEAM_ID/installedApps/$INSTALL_ID" \
-H "Authorization: Bearer $TOKEN"
curl -s -X POST \
"https://graph.microsoft.com/v1.0/teams/$TEAM_ID/installedApps" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"teamsApp@odata.bind\": \"https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/$TEAMS_APP_CATALOG_ID\"}" | jq .
Example Root Cause Analysis
A Teams bot deployed to read channel messages and post summaries starts returning 403 after a version update adds support for private channel access. The bot works in some teams but fails in others.
Checking the installed version in a failing team:
curl -s "https://graph.microsoft.com/v1.0/teams/$TEAM_ID/installedApps?$expand=teamsAppDefinition" \
-H "Authorization: Bearer $TOKEN" | jq '.value[] | select(.teamsApp.displayName=="SummaryBot") | .teamsAppDefinition.version'
"1.3.0"
The catalog has version 1.4.0 which added ChannelMessage.Read.Group and Channel.ReadBasic.Group. Teams where the bot was installed before the update still have 1.3.0 and its original (narrower) consent. The bot is using an application token with RSC — no roles, no delegated scopes — so access is purely RSC-based.
Fix: use the Graph API to upgrade all affected installations:
for TEAM_ID in $(cat affected-teams.txt); do
INSTALL_ID=$(curl -s "https://graph.microsoft.com/v1.0/teams/$TEAM_ID/installedApps?$expand=teamsApp" \
-H "Authorization: Bearer $TOKEN" | jq -r '.value[] | select(.teamsApp.displayName=="SummaryBot") | .id')
curl -s -X POST \
"https://graph.microsoft.com/v1.0/teams/$TEAM_ID/installedApps/$INSTALL_ID/upgrade" \
-H "Authorization: Bearer $TOKEN"
echo "Upgraded $TEAM_ID"
done
After upgrade, team owners are prompted in Teams to accept the new RSC permissions, and the bot begins reading channel messages successfully.
Prevention Best Practices
- Pin RSC permission requirements in your manifest explicitly and review the full
authorization.permissions.resourceSpecificblock at every release — adding a permission is a breaking change that requires re-consent. - Automate a post-deploy check that calls
/teams/{id}/installedAppsand compares the installed version against the catalog version; alert if any team is running an outdated installation. - Use exact
"Application"(capital A) and the full dotted permission name (e.g.,ChannelMessage.Read.Group) — Teams silently ignores malformed entries without surfacing an error at install time. - Separate
.Group(team-scoped) and.Chat(chat-scoped) permissions carefully; never assume a.Grouppermission covers chat endpoints. - Maintain an installation inventory: track which teams have your app installed and at which consent version so you can identify affected teams immediately after a manifest update.
- The incident response assistant can correlate 403
MissingRscPermissionerrors with specific manifest versions and installation states to speed up triage.
Quick Command Reference
# List installed apps in a team and their versions
curl -s "https://graph.microsoft.com/v1.0/teams/$TEAM_ID/installedApps?$expand=teamsApp,teamsAppDefinition" \
-H "Authorization: Bearer $TOKEN" | jq '.value[] | {name:.teamsApp.displayName, version:.teamsAppDefinition.version, id:.id}'
# Inspect RSC permissions in a local manifest
unzip -p myteamsbot.zip manifest.json | jq '.authorization.permissions.resourceSpecific'
# Check latest version in tenant app catalog
curl -s "https://graph.microsoft.com/v1.0/appCatalogs/teamsApps?$filter=externalId eq '$APP_EXTERNAL_ID'&$expand=appDefinitions" \
-H "Authorization: Bearer $TOKEN" | jq '.value[].appDefinitions | last | {version, publishingState}'
# Upgrade app installation (re-triggers RSC consent)
curl -s -X POST "https://graph.microsoft.com/v1.0/teams/$TEAM_ID/installedApps/$INSTALL_ID/upgrade" \
-H "Authorization: Bearer $TOKEN"
# Remove an app installation
curl -s -X DELETE "https://graph.microsoft.com/v1.0/teams/$TEAM_ID/installedApps/$INSTALL_ID" \
-H "Authorization: Bearer $TOKEN"
# Install app from tenant catalog
curl -s -X POST "https://graph.microsoft.com/v1.0/teams/$TEAM_ID/installedApps" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"teamsApp@odata.bind\": \"https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/$APP_CATALOG_ID\"}"
# Decode token to check if application or delegated context
echo "$TOKEN" | cut -d'.' -f2 | base64 -d 2>/dev/null | jq '{token_type: (if .scp then "delegated" else "application" end), roles, scp}'
# Get a fresh application token (client credentials)
az account get-access-token --resource https://graph.microsoft.com --query accessToken -o tsv
Conclusion
A Forbidden / MissingRscPermission error from Graph on Teams endpoints is almost always one of six root causes:
- The required RSC permission is absent from the app manifest’s
authorization.permissions.resourceSpecificblock. - The team’s installation predates the manifest update that added the permission — re-consent was never triggered.
- The app is using a delegated token where RSC does not apply, or an application token where no tenant-wide permissions were granted alongside RSC.
- A
.Group-scoped permission was used against a chat endpoint (or vice versa) — RSC permissions are not interchangeable across team and chat scopes. - The app is installed in a different team from the one being accessed — RSC grants are per-installation, not tenant-wide.
- A malformed manifest entry (wrong type casing, missing dot in permission name) caused the grant to be silently ignored at install time.
Start with the error message’s named permission, verify it in the manifest, confirm the installed version matches the current catalog version, and reinstall if needed.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.