Skip to content
CloudOps
Newsletter
All guides
AI for Microsoft Teams By James Joyner IV · · 9 min read

Automating Teams and Channel Provisioning With RSC Permissions

Spin up incident channels and project teams on demand, and let your app act on them with resource-specific consent instead of broad tenant-wide Graph scopes.

  • #microsoft-teams
  • #provisioning
  • #rsc
  • #graph-api
  • #automation
  • #incident-response

When a SEV1 fires, the last thing anyone should be doing is clicking through Teams to create a war-room channel, add the right people, and pin the runbook. That should be one API call. Same for onboarding a new project team. The catch is permissions: provisioning teams and channels and then having your app operate inside them usually means requesting scary tenant-wide Graph scopes that admins (rightly) refuse to grant. Resource-Specific Consent (RSC) is the escape hatch — it lets your app act on just the teams that installed it, no global grant required. Here’s how I provision on demand without becoming a tenant-wide security risk.

The two halves: create, then operate

Provisioning has two distinct permission problems people conflate:

  1. Creating a team or channel — a one-time, privileged operation.
  2. Operating inside it afterward — posting cards, reading messages, managing membership.

For creation you can use a background app-permission flow (or have an admin-approved automation account do it). For operating, RSC is the right tool, because it scopes the app’s powers to the specific team where it’s installed instead of every team in the tenant.

Creating a channel on demand via Graph

The incident bot creates the war-room channel with a single Graph call. A standard channel under the incident team:

async function createIncidentChannel(teamId, incident) {
  return graph.api(`/teams/${teamId}/channels`).post({
    displayName: `inc-${incident.id}-${incident.shortName}`,
    description: `War room for ${incident.summary}`,
    membershipType: "standard",
  });
}

For a fully separate team (a long-lived project space), you POST /teams from a group template, then add channels and members. For incidents I prefer channels under one standing “Incidents” team — creating a brand-new team per incident leaves dozens of orphaned teams to clean up later. A channel is cheap to archive; a team is governance debt.

RSC permissions are declared in the app manifest under authorization.permissions.resourceSpecific. When a team owner installs your app into their team, they consent to exactly these — and they apply only to that team:

{
  "authorization": {
    "permissions": {
      "resourceSpecific": [
        { "name": "ChannelMessage.Send.Group", "type": "Application" },
        { "name": "ChannelSettings.Read.Group", "type": "Application" },
        { "name": "TeamMember.Read.Group", "type": "Application" }
      ]
    }
  }
}

Now your app can post messages and read membership in that team without holding ChannelMessage.Send tenant-wide. The difference matters enormously to security review: “this app can post in teams that install it” is an easy approval; “this app can post in every channel in the company” is a hard no.

Application vs delegated RSC

RSC comes in two flavors and the distinction trips people up:

  • Application RSC — the app acts as itself, no signed-in user. Right for a provisioning/notification service that runs unattended.
  • Delegated RSC — the app acts as the signed-in user within the resource. Right when actions must be attributed to a person.

For an incident-provisioning bot, application RSC is usually what you want: it runs on a webhook, not a human session. Mix in delegated only where you genuinely need “user X did this” semantics.

Add the right people automatically

An empty war room is useless. After creating the channel, add the on-call rotation and the service owners. With Graph you add members by their AAD ID:

async function addMembers(teamId, channelId, aadIds) {
  for (const id of aadIds) {
    await graph.api(`/teams/${teamId}/channels/${channelId}/members`).post({
      "@odata.type": "#microsoft.graph.aadUserConversationMember",
      "user@odata.bind": `https://graph.microsoft.com/v1.0/users('${id}')`,
      roles: [],
    });
  }
}

Pull the on-call IDs from your scheduling tool, the owners from your service catalog, and the channel is staffed before a human notices the incident.

Provision the contents, not just the container

The channel is the boring part. The value is what you seed into it: pin the runbook tab, post the incident summary card, and drop a deep link back to the alert. A provisioning routine should be: create channel → add members → pin runbook tab → post the kickoff adaptive card. That turns “here’s an empty room, go” into “here’s everything you need, start working.”

Clean up, or drown in channels

On-demand creation needs on-demand teardown or your tenant fills with stale inc-* channels. Two habits:

  • Archive on resolve. When the incident closes, archive (don’t delete) the channel so the postmortem record survives but it drops out of active lists.
  • Tag for lifecycle. Stamp a created-by-automation marker (in the description or a backing store) so a scheduled job can sweep channels past a retention window. Manual cleanup never happens; a scheduled sweep does.

Where this fits

On-demand provisioning turns the war-room ritual into one API call, and RSC is what makes it safe to grant — scoped powers on installed teams instead of a tenant-wide blank check. Create channels under a standing team, request the narrowest RSC permissions, staff and seed the room automatically, and archive on resolve. For Graph snippets and the war-room kickoff card, see the prompt library, and find more provisioning and incident patterns in the Microsoft Teams category.

Graph endpoints and RSC permission names change; verify the current resource-specific consent catalog and channel APIs against Microsoft Graph docs before automating creation.

Free download · 368-page PDF

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.