Microsoft Teams¶
client.teams is Microsoft Graph's Teams API: teams, channels, channel
messages, team members, installed apps, tags — plus the team-scoped operations
that only make sense next to a team. This folder is organised by resource,
so each subfolder answers one question.
Teams is the one product where creation is asynchronous. Creating (or cloning) a team returns a
teamsAsyncOperation; shared and private channel creation is also accepted with202 Acceptedand polled. The library wraps this increate_and_wait/clone_and_wait, andChannelCollection.addpolls the operation for you. See Async & long-running operations.
The model¶
flowchart LR
T["teams/"] --> A["administration<br/>create · settings · clone"]
T --> M["members<br/>owners, members, guests"]
T --> C["channels<br/>+ messages"]
T --> H["chats<br/>1:1 & group"]
T --> AP["apps<br/>catalog & tabs"]
T --> TG["tags"]
T --> G["audit · governance<br/>inventory, cleanup"]
T --> E["export · migration<br/>backup & restore"]
Start here¶
cp .env.example .env
uv run python examples/teams/quickstart.py # list teams
uv run python examples/teams/quickstart.py --create # create + delete a team
→ quickstart.py connects, lists the tenant's teams, and
shows the one asynchronous call (create_and_wait). If it prints teams, your
consent is fine.
The basics — one team¶
administration/ · Team.Create, TeamSettings.ReadWrite.All, Directory.ReadWrite.All
The core lifecycle: create, rename, change settings, archive and delete. Team
creation is async, so create_and_wait() submits the operation, polls it and
returns the provisioned team from a single execute_query().
team = client.teams.create_and_wait(
display_name="Finance",
description="Budgeting and reporting",
owners=["owner@contoso.com"], # required for app-only creation
).execute_query()
print(team.id, team.display_name)
| Script | What it does |
|---|---|
administration/lifecycle.py |
create · update · settings · archive · unarchive · delete |
administration/clone.py |
Clone a team and wait for the copy (clone_and_wait) |
administration/settings_audit.py |
Compare every team's settings against a policy baseline |
Membership¶
members/ · TeamMember.Read.All · TeamMember.ReadWrite.All
Team members are ConversationMember entities with a roles list. Adding an
owner means sending a member with roles: ["owner"]; the library builds that
payload for you.
team.members.add(user, roles=["owner"]).execute_query()
for member in team.members.get().execute_query():
print(member.display_name, member.email, list(member.roles or []))
| Script | What it does |
|---|---|
members/lifecycle.py |
list · add · set-role · remove |
members/guest_audit.py |
Teams with external guests (#EXT#) |
members/audit.py |
Orphaned teams (no owner) and over-owned teams |
Channels¶
channels/ · Channel.ReadBasic.All · Channel.Create · ChannelMessage.Read.All
A team owns channels; the General channel is the primary one and cannot be
deleted. Channel membership type (standard, private, shared) is fixed at
creation. Shared channels are shared with other teams rather than adding everyone
as a guest — see channels/shared.py.
channel = team.channels.add("Project Falcon", description="Delivery").execute_query()
messages = channel.messages.get_all().top(20).execute_query()
| Script | What it does |
|---|---|
channels/lifecycle.py |
list · create · update · delete · primary · channel email |
channels/messages.py |
send · reply · list · per-channel activity digest |
channels/inventory.py |
Cross-team channel inventory (flags private/shared) |
channels/inactive.py |
Channels that have gone quiet |
channels/shared.py |
Shared channels: create, share with another team, check access |
Chats¶
chats/ · Chat.ReadWrite.All (application) · Chat.ReadWrite (delegated)
Chats are not under a team: they are first-class, user-relative resources
(client.users[...].chats). One-on-one and group chats have messages, members
and a topic, but no channels and no threaded replies.
| Script | What it does |
|---|---|
chats/inventory.py |
A user's chats, by type, with last activity |
chats/lifecycle.py |
create-one-on-one · create-group · members · add-member · delete |
chats/messages.py |
send · list · export · activity digest |
Apps & tabs¶
apps/ · TeamsAppInstallation.Read.All · AppCatalog.Read.All · Tab.Read.All
The app catalog is the tenant store (built-in, store, sideloaded). An installation is an app added to a team. A tab is a pinned app in a channel — the same app surfaced in the UI.
| Script | What it does |
|---|---|
apps/catalog.py |
Inventory of the tenant app catalog |
apps/installed.py |
App adoption across all teams (shadow-IT detection) |
apps/lifecycle.py |
search · list · install · uninstall |
apps/tabs.py |
Tabs (pinned apps) across teams → channels |
Tags¶
tags/ · TeamworkTag.Read.All · TeamworkTag.ReadWrite.All
A tag is a named group of members ("Designers") that can be @mentioned in
a channel, so tagging is a per-team membership feature rather than a directory
object.
| Script | What it does |
|---|---|
tags/lifecycle.py |
create · list · assign · unassign · members · delete |
tags/report.py |
All tags across teams + teams without tags |
Audit & governance¶
Read-only reports for ownership, guests, settings drift, empty/inactive teams
and soft-deleted groups. Anything destructive is a dry run by default and
only changes state with --apply.
| Script | What it does |
|---|---|
audit/inventory.py |
Tenant-wide inventory with membership breakdown |
audit/empty.py |
Teams with no channels, and channels that are silent |
audit/lifecycle.py |
Archived teams and recently deleted (restorable) groups |
governance/inactive.py |
report / archive / delete teams idle ≥ --days |
governance/restore.py |
List, restore, or purge a soft-deleted group |
Export, import & migration¶
export/ writes the tenant inventory as CSV/JSON and provisions teams from CSV.
migration/ is the heavier toolkit: a resumable MigrationJob that archives a
team's structure, members, channels, tabs, apps, messages and (optionally) files
into a portable folder.
| Script | What it does |
|---|---|
export/inventory.py |
Export tenant inventory to CSV + JSON |
export/memberships.py |
One row per team member (team,name,email,roles) |
export/provision.py |
Bulk-provision teams from CSV (--dry-run) |
migration/migrate/export_team.py |
Resumable team archive via MigrationJob (see migration/README.md) |
migration/migrate/export_messages.py |
Compliance message export (to_ndjson + hosted contents) |
Usage reports & async patterns¶
Teams usage analytics live with the rest of the reporting endpoints, and the
reusable asynchronous patterns live under patterns/:
../reports/teams/usage.py— team counts and user activity over periods../reports/usage_reports.py— the wider Graph reporting surface../patterns/async/provision_teams_async.py— provision many teams concurrently../patterns/lro/wait_team_clone_async.py— clone a team without blocking the loop
Async & long-running operations¶
Two Teams operations are not synchronous, and the library hides the polling
behind *_wait helpers:
| Operation | Synchronous helper | Explicit form |
|---|---|---|
| Create a team | client.teams.create_and_wait(...) |
create(...) → poll the TeamsAsyncOperation |
| Clone a team | team.clone_and_wait(...) |
team.clone(...) → poll_for_status() |
| Create a private/shared channel | channel = team.channels.add(..., membership_type="shared") |
polls the 202 operation inside add() |
The explicit, awaitable versions are in
../patterns/lro/ (async twin of
administration/clone.py). App-only team creation
should pass owners=[...] — Graph rejects an owner-less team in application
context.
Permissions at a glance¶
| Area | Application | Delegated |
|---|---|---|
| Read teams / channels | Team.ReadBasic.All, Channel.ReadBasic.All |
same |
| Create / rename / delete | Group.ReadWrite.All, Team.Create |
Team.Create |
| Settings & archive | TeamSettings.ReadWrite.All |
same |
| Members | TeamMember.Read.All / ReadWrite.All |
same |
| Channel messages | ChannelMessage.Read.All; send needs ChannelMessage.Send.All |
read needs ChannelMessage.Read.All, send ChannelMessage.ReadWrite |
| Chats | Chat.Read.All / Chat.ReadWrite.All |
Chat.Read / Chat.ReadWrite |
| Apps & tabs | TeamsAppInstallation.Read.All, AppCatalog.Read.All |
same |
| Tags | TeamworkTag.Read.All / ReadWrite.All |
same |
The example scripts read credentials and the test account from tests/settings
(via .env). Where a permission is missing they print the missing scope and
exit cleanly rather than crashing.
Related tours¶
Online meetings, presence and call records look like Teams features but belong to
Graph's cloud communications namespace, so they have their own tour:
../communications/. The reporting endpoints live in
../reports/.