This Python client interfaces directly with Microsoft Teams' internal (unofficial) APIs — the same APIs used by the Teams web client at teams.live.com. It bypasses the official Microsoft Graph API entirely.
WARNING: These APIs are undocumented and may change without notice. This is for educational/research purposes.
pip install requests websocket-client rich questionary| Package | Purpose |
|---|---|
requests |
HTTP API calls |
websocket-client |
Trouter real-time WebSocket connection |
rich |
Tables, panels, colored output, loading spinners |
questionary |
Arrow-key menus, checkbox multi-select, file path autocomplete |
Optional (for automated browser login):
pip install playwright
playwright install chromiumpython teams_internal_api.pyThis launches an interactive terminal with arrow-key navigation:
╔══════════════════════════════════╗
║ Microsoft Teams CLI Client ║
╚══════════════════════════════════╝
? What would you like to do?
> Chat (select user)
List conversations
Read messages
Send a message
Check user presence
Set my status
Search messages
Create a new chat
Start real-time listener
Upload a file
Exit
- Arrow-key navigation — select options with Up/Down, confirm with Enter
- "< Back" on every screen — return to the previous menu from anywhere
- Ctrl+C safe — cancels current operation, returns to main menu
- Rich tables — formatted output with colors, icons, and borders
- Loading spinners — animated feedback during API calls
- Breadcrumb headers — always shows where you are (e.g.,
Main Menu > Chat > John)
Selecting any conversation opens an interactive chat view:
─── Main Menu > Chat > John Doe ───
05/03 14:30 John Doe Hey, how's it going?
05/03 14:32 You Good, working on the project
05/03 14:35 John Doe Great, let me know if you need help
Type a message and press Enter to send. Commands: /file /presence /refresh /back
Auto-refresh every 5s. New messages appear automatically.
>
Chat commands:
| Command | Action |
|---|---|
/back |
Return to previous menu |
/refresh |
Manually reload messages |
/file |
Upload a file (with tab-autocomplete for paths) |
/presence |
Check online status of chat members |
| Any text | Send as a message |
On first run, the CLI auto-detects there's no saved session and starts authentication:
Option A — Automated browser extraction (recommended, requires Playwright):
client = TeamsClient()
client.authenticate_via_browser()
# Opens Chromium, user logs in, refresh token extracted automaticallyOption B — Interactive OAuth2 + PKCE:
client = TeamsClient()
client.authenticate()
# Opens browser for login, user pastes redirect URLOption C — Manual refresh token:
client = TeamsClient()
client.authenticate_with_refresh_token("0.AX4A...")Tokens are saved to ~/.teams_tokens.json and reloaded automatically:
client = TeamsClient()
if client.load_session():
print("Ready!")The client auto-detects whether the account is consumer (personal Microsoft) or enterprise (organization) by extracting the tid claim from the JWT id_token. The token endpoint adjusts automatically:
- Consumer:
login.microsoftonline.com/consumers/oauth2/v2.0/token - Enterprise:
login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
Teams internally uses 7 different OAuth2 tokens for different microservices:
| Service | Endpoint | Token Scope |
|---|---|---|
| Chat (Messaging) | msgapi.teams.live.com |
service::api.fl.spaces.skype.com::MBI_SSL |
| Middle Tier | teams.live.com/api/mt |
https://mtsvc.fl.teams.microsoft.com/teams.mt.readwrite |
| Groups | teams.live.com/api/groups |
https://groupssvc.fl.teams.microsoft.com/teams.readwrite |
| Presence | presence.teams.live.com |
(uses Skype token) |
| Search | teams.live.com/api/search |
https://searchsvc.fl.teams.microsoft.com/teams.search.readwrite |
| Media/Files | us-api.asm.skype.com |
(uses Skype token) |
| Profile | teams.live.com/api/mt |
(uses MT token) |
The client ID 4b3e8f46-56d3-427f-b1e2-d239b2ea6bca is Microsoft's own Teams web client registration (SPA type).
# List all conversations
conversations = client.get_conversations(count=25)
for conv in conversations:
print(f"{conv.title} - {conv.type} - {conv.id}")
# Get conversation details
details = client.chat.get_conversation_details("19:xxx@thread.v2")# Read messages
messages = client.get_messages("19:xxx@thread.v2", count=50)
for msg in messages:
print(f"[{msg.display_name}]: {msg.content}")
# Send a text message
client.send_message("19:xxx@thread.v2", "Hello from Python!")
# Send HTML-formatted message
client.chat.send_message(
"19:xxx@thread.v2",
"<p><b>Bold text</b> and <i>italic</i></p>",
message_type="RichText/Html"
)
# Reply to a message
client.chat.send_reply("19:xxx@thread.v2", "1769692433796", "This is a reply")
# Edit a message
client.chat.edit_message("19:xxx@thread.v2", "1769692433796", "Updated content")
# Delete a message
client.chat.delete_message("19:xxx@thread.v2", "1769692433796")
# Send typing indicator
client.chat.send_typing_indicator("19:xxx@thread.v2")
# Mark messages as read
client.chat.set_consumption_horizon("19:xxx@thread.v2", "1769692433796")
# Add reaction
client.chat.add_reaction("19:xxx@thread.v2", "1769692433796", "like")# Create 1:1 chat
thread_id = client.chat.create_chat(
member_mris=["8:live:.cid.abc123"],
message="Hey! Starting a new chat."
)
# Create group chat with topic
thread_id = client.chat.create_chat(
member_mris=["8:live:.cid.abc123", "8:live:.cid.def456"],
topic="Project Discussion",
message="Welcome everyone!"
)
# Add/remove members
client.chat.add_member("19:xxx@thread.v2", "8:live:.cid.newuser")
client.chat.remove_member("19:xxx@thread.v2", "8:live:.cid.olduser")
# Set chat topic
client.chat.set_topic("19:xxx@thread.v2", "New Topic Name")# Get all contacts (API + conversation-extracted)
contacts = client.contacts.get_all_contacts(client.chat)
for c in contacts:
print(f"{c['displayName']} - {c['mri']}")
# Get contacts from API only
api_contacts = client.contacts.fetch_contacts()
# Get contacts from recent conversations
conv_contacts = client.contacts.get_contacts_from_conversations(client.chat)# Check user presence
presences = client.get_user_presence([
"8:live:.cid.abc123",
"8:live:.cid.def456"
])
for p in presences:
print(f"{p.mri}: {p.availability} on {p.device_type}")
# Set your status
client.set_status("Available") # Available, Away, Busy, DoNotDisturb, Offline
# Set custom status message
client.presence.set_status_message("In a meeting", expiry_minutes=60)
# Subscribe to presence changes
client.presence.subscribe_to_presence(["8:live:.cid.abc123"])# Search messages across recent conversations
results = client.search_messages("project update")
for r in results:
print(f"[{r['sender']}] in {r['conversation']}: {r['content']}")# Upload a file
result = client.media.upload_file(
conversation_id="19:xxx@thread.v2",
file_path="/path/to/document.pdf"
)
print(f"Object ID: {result['object_id']}")
# Download a file
client.media.download_file("object_id_here", "/path/to/save/file.pdf")# Get tenant info
tenants = client.middle_tier.get_tenants()
# Get user profile
profile = client.middle_tier.get_user_profile()# Define callback
def on_new_message(url, body, headers):
event = parse_trouter_event(url, body, headers)
if event and event["type"] == "message":
print(f"[NEW] {event['from']}: {event['content'][:80]}")
# Start listening (uses Trouter WebSocket push notifications)
client.start_realtime(on_message=on_new_message)
# ... your app runs ...
# Stop listening
client.stop_realtime()| Service | URL |
|---|---|
| Chat Service | https://msgapi.teams.live.com |
| Chat Service (AFD) | https://teams.live.com/api/chatsvc/consumer |
| Chat Aggregator | https://chatsvcagg.teams.microsoft.com |
| Middle Tier | https://teams.live.com/api/mt |
| Groups V2 | https://teams.live.com/api/groups |
| Presence | https://presence.teams.live.com |
| Presence UPS | https://teams.live.com/ups/global |
| Search | https://teams.live.com/api/search |
| Search (Legacy) | https://msgsearch.skype.com |
| Media (AMS) | https://us-api.asm.skype.com |
| User Intelligence | https://teams.live.com/api/nss |
| User Profile | https://teams.microsoft.com/api/userprofilesvc/amer |
| Vault/GraphQL | https://teams.live.com/api/datalayer/vault/graphql |
| OpenAI | https://teams.live.com/api/openai |
| Events | https://teams.live.com/api/events |
| URL Preview | https://urlp.asm.skype.com |
| Auth Bootstrap | https://teams.live.com/api/auth/v1.0/authz/consumer |
- Personal/Consumer:
8:live:.cid.{hex_cid} - MSS accounts:
8:mss.{username} - Org/AAD accounts:
8:orgid:{guid}
- Legacy Skype:
19:{hex}@thread.skype - New V2:
19:uni01_{base32}@thread.v2
Text— Plain textRichText/Html— HTML formattedRichText/Media_GenericCard— Cards/attachmentsEvent/Call— Call eventsThreadActivity/MemberJoined— Member join notificationsThreadActivity/TopicUpdate— Topic change eventsControl/Typing— Typing indicators
- User opens OAuth2 URL with Teams client ID (
4b3e8f46...) and PKCE challenge - Microsoft login returns an authorization
code - Code is exchanged for
access_token+refresh_tokenat/oauth2/v2.0/token(with PKCE verifier) - Tenant ID is auto-detected from the
id_tokenJWTtidclaim - The refresh token acquires service-specific tokens for each backend
- For Chat/Messaging: an additional Skype token is obtained from
/api/auth/v1.0/authz/consumer - Tokens are stored locally (
~/.teams_tokens.json) and refreshed automatically
The client uses Microsoft's Trouter protocol for real-time push notifications:
- Allocate —
POST /v4/ato get a Trouter session (socketio URL, surl, registrar URL) - WebSocket Connect — Connect to
wss://<host>/v4/cusing socket.io v0.9 protocol - Authenticate — Send
user.authenticateevent with Skype token and connect params - Register — After
trouter.connectedevent, register with the PNH registrar using three template keys:TFLSkypeSpacesWeb_2.0(primary)TFLSkypeSpacesWeb_1.0(fallback)SkypeSpacesWeb_2.3(catch-all)
- Receive — Incoming messages arrive as type-3 socket.io packets, must be ACKed immediately
The Teams web client uses 82+ IndexedDB databases including:
conversation-manager(1101 conversations cached)replychain-manager(5696 message chains cached)presence-manager(30 user presence records)syncstate-manager(sync tokens and cursors)messaging-slice-manager(threads, mentions, drafts, saved items)profiles(user profile cache)
| Class | Purpose |
|---|---|
TokenManager |
OAuth2 token lifecycle, PKCE, tenant auto-detection |
ChatService |
Conversations, messages, reactions, typing indicators |
MiddleTierService |
Tenant info, user profiles |
PresenceService |
Online status, status messages |
SearchService |
Message search across conversations |
ContactsService |
Contact list from API and conversation extraction |
MediaService |
File upload/download via AMS |
TrouterService |
Real-time WebSocket push notifications |
TeamsClient |
Unified client combining all services |
This project reverse-engineers Microsoft Teams' internal APIs for educational purposes. These APIs are undocumented, subject to change, and not covered by any SLA. Use of these APIs may violate Microsoft's Terms of Service. Use at your own risk.