Install
openclaw skills install microsoft-teamsMicrosoft Teams API integration with managed OAuth. Manage teams, channels, messages, and meetings via Microsoft Graph API. Use this skill when users want to list teams, create channels, send messages, schedule meetings, or access meeting recordings and transcripts. For other third party apps, use the api-gateway skill (https://clawhub.ai/byungkyu/api-gateway).
openclaw skills install microsoft-teamsAccess the Microsoft Teams API with managed OAuth authentication via Microsoft Graph. Manage teams, channels, messages, meetings, and access recordings and transcripts.
# List user's joined teams
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/microsoft-teams/v1.0/me/joinedTeams')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
https://api.maton.ai/microsoft-teams/{native-api-path}
Maton proxies requests to graph.microsoft.com and automatically injects your OAuth token.
All requests require the Maton API key in the Authorization header:
Authorization: Bearer $MATON_API_KEY
Environment Variable: Set your API key as MATON_API_KEY:
export MATON_API_KEY="YOUR_API_KEY"
Manage your Microsoft Teams OAuth connections at https://api.maton.ai.
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections?app=microsoft-teams&status=ACTIVE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
python <<'EOF'
import urllib.request, os, json
data = json.dumps({'app': 'microsoft-teams'}).encode()
req = urllib.request.Request('https://api.maton.ai/connections', data=data, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections/{connection_id}')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
{
"connection": {
"connection_id": "{connection_id}",
"status": "ACTIVE",
"creation_time": "2026-02-17T09:51:21.074601Z",
"last_updated_time": "2026-02-17T09:51:34.323814Z",
"url": "https://connect.maton.ai/?session_token=...",
"app": "microsoft-teams",
"metadata": {}
}
}
Open the returned url in a browser to complete OAuth authorization.
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections/{connection_id}', method='DELETE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If you have multiple Microsoft Teams connections, specify which one to use with the Maton-Connection header:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/microsoft-teams/v1.0/me/joinedTeams')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Maton-Connection', '{connection_id}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If you have multiple connections, always include this header to ensure requests go to the intended account.
GET /microsoft-teams/v1.0/me/joinedTeams
Response:
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#teams",
"@odata.count": 1,
"value": [
{
"id": "b643f103-870d-4f98-a23d-e6f164fae33e",
"displayName": "carvedai.com",
"description": null,
"isArchived": false,
"tenantId": "cb83c3f9-6d16-4cf3-bd8c-ab16b37932f9"
}
]
}
GET /microsoft-teams/v1.0/teams/{team-id}
GET /microsoft-teams/v1.0/teams/{team-id}/channels
Response:
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#teams('...')/channels",
"@odata.count": 1,
"value": [
{
"id": "19:9fwtZjo3IM0D8bLdQqR-_oMFw1eUDlzWjPfIhNGhVd41@thread.tacv2",
"createdDateTime": "2026-02-16T20:09:27.254Z",
"displayName": "General",
"description": null,
"email": "carvedai.com473@carvedai.com",
"membershipType": "standard",
"isArchived": false
}
]
}
GET /microsoft-teams/v1.0/teams/{team-id}/channels?$filter=membershipType eq 'private'
GET /microsoft-teams/v1.0/teams/{team-id}/channels/{channel-id}
POST /microsoft-teams/v1.0/teams/{team-id}/channels
Content-Type: application/json
{
"displayName": "New Channel",
"description": "Channel description",
"membershipType": "standard"
}
Response:
{
"id": "19:3b3361df822044558a062bb1a4ac8357@thread.tacv2",
"createdDateTime": "2026-02-17T20:24:33.9284462Z",
"displayName": "Maton Test Channel",
"description": "Channel created by Maton integration test",
"membershipType": "standard",
"isArchived": false
}
PATCH /microsoft-teams/v1.0/teams/{team-id}/channels/{channel-id}
Content-Type: application/json
{
"description": "Updated description"
}
Returns 204 No Content on success. Note: The default "General" channel cannot be updated.
DELETE /microsoft-teams/v1.0/teams/{team-id}/channels/{channel-id}
Returns 204 No Content on success.
GET /microsoft-teams/v1.0/teams/{team-id}/channels/{channel-id}/members
Response:
{
"@odata.count": 1,
"value": [
{
"@odata.type": "#microsoft.graph.aadUserConversationMember",
"id": "MCMjMiMj...",
"roles": ["owner"],
"displayName": "Kevin Kim",
"userId": "5f56d55b-2ffb-448d-982a-b52547431f71",
"email": "richard@carvedai.com"
}
]
}
GET /microsoft-teams/v1.0/teams/{team-id}/channels/{channel-id}/messages
POST /microsoft-teams/v1.0/teams/{team-id}/channels/{channel-id}/messages
Content-Type: application/json
{
"body": {
"content": "Hello World"
}
}
Response:
{
"id": "1771359569239",
"replyToId": null,
"messageType": "message",
"createdDateTime": "2026-02-17T20:19:29.239Z",
"importance": "normal",
"locale": "en-us",
"from": {
"user": {
"id": "5f56d55b-2ffb-448d-982a-b52547431f71",
"displayName": "Kevin Kim",
"userIdentityType": "aadUser",
"tenantId": "cb83c3f9-6d16-4cf3-bd8c-ab16b37932f9"
}
},
"body": {
"contentType": "text",
"content": "Hello World"
},
"channelIdentity": {
"teamId": "b643f103-870d-4f98-a23d-e6f164fae33e",
"channelId": "19:9fwtZjo3IM0D8bLdQqR-_oMFw1eUDlzWjPfIhNGhVd41@thread.tacv2"
}
}
POST /microsoft-teams/v1.0/teams/{team-id}/channels/{channel-id}/messages
Content-Type: application/json
{
"body": {
"contentType": "html",
"content": "<h1>Hello</h1><p>This is <strong>formatted</strong> content.</p>"
}
}
POST /microsoft-teams/v1.0/teams/{team-id}/channels/{channel-id}/messages/{message-id}/replies
Content-Type: application/json
{
"body": {
"content": "This is a reply"
}
}
GET /microsoft-teams/v1.0/teams/{team-id}/channels/{channel-id}/messages/{message-id}/replies
PATCH /microsoft-teams/v1.0/teams/{team-id}/channels/{channel-id}/messages/{message-id}
Content-Type: application/json
{
"body": {
"content": "Updated message content"
}
}
Returns 204 No Content on success.
GET /microsoft-teams/v1.0/teams/{team-id}/members
Response:
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#teams('...')/members",
"@odata.count": 1,
"value": [
{
"@odata.type": "#microsoft.graph.aadUserConversationMember",
"id": "MCMjMSMj...",
"roles": ["owner"],
"displayName": "Kevin Kim",
"userId": "5f56d55b-2ffb-448d-982a-b52547431f71",
"email": "richard@carvedai.com",
"tenantId": "cb83c3f9-6d16-4cf3-bd8c-ab16b37932f9"
}
]
}
GET /microsoft-teams/v1.0/me/presence
Response:
{
"id": "5f56d55b-2ffb-448d-982a-b52547431f71",
"availability": "Offline",
"activity": "Offline",
"outOfOfficeSettings": {
"message": null,
"isOutOfOffice": false
}
}
Availability values: Available, Busy, DoNotDisturb, Away, Offline
GET /microsoft-teams/v1.0/users/{user-id}/presence
Returns presence information for a specific user by their ID.
GET /microsoft-teams/v1.0/teams/{team-id}/channels/{channel-id}/tabs
Response:
{
"@odata.count": 2,
"value": [
{
"id": "ee0b3e8b-dfc8-4945-a45d-28ceaf787d92",
"displayName": "Notes",
"webUrl": "https://teams.microsoft.com/l/entity/..."
},
{
"id": "3ed5b337-c2c9-4d5d-b7b4-84ff09a8fc1c",
"displayName": "Files",
"webUrl": "https://teams.microsoft.com/l/entity/..."
}
]
}
GET /microsoft-teams/v1.0/teams/{team-id}/installedApps
POST /microsoft-teams/v1.0/me/onlineMeetings
Content-Type: application/json
{
"subject": "Team Sync",
"startDateTime": "2026-02-18T10:00:00Z",
"endDateTime": "2026-02-18T11:00:00Z"
}
Response:
{
"id": "MSo1ZjU2ZDU1Yi0yZmZi...",
"subject": "Team Sync",
"startDateTime": "2026-02-18T10:00:00Z",
"endDateTime": "2026-02-18T11:00:00Z",
"joinUrl": "https://teams.microsoft.com/l/meetup-join/...",
"joinWebUrl": "https://teams.microsoft.com/l/meetup-join/...",
"meetingCode": "28636743235745",
"joinMeetingIdSettings": {
"joinMeetingId": "28636743235745",
"passcode": "qh37NK9V",
"isPasscodeRequired": true
},
"participants": {
"organizer": {
"upn": "richard@carvedai.com",
"role": "presenter"
}
}
}
The joinUrl can be shared with attendees to join the meeting.
GET /microsoft-teams/v1.0/me/onlineMeetings/{meeting-id}
GET /microsoft-teams/v1.0/me/onlineMeetings?$filter=JoinWebUrl eq '{encoded-join-url}'
Note: Microsoft Graph requires a filter to query meetings. You cannot list all meetings without filtering by JoinWebUrl.
GET /microsoft-teams/v1.0/me/calendar/events?$top=10
Scheduled Teams meetings appear as calendar events with isOnlineMeeting: true.
DELETE /microsoft-teams/v1.0/me/onlineMeetings/{meeting-id}
Returns 204 No Content on success.
POST /microsoft-teams/v1.0/me/onlineMeetings
Content-Type: application/json
{
"subject": "Project Review",
"startDateTime": "2026-02-18T14:00:00Z",
"endDateTime": "2026-02-18T15:00:00Z",
"participants": {
"attendees": [
{
"upn": "attendee@example.com",
"role": "attendee"
}
]
}
}
GET /microsoft-teams/v1.0/me/onlineMeetings/{meeting-id}/recordings
Returns a list of recordings for a meeting (available after the meeting has ended and recording was enabled).
GET /microsoft-teams/v1.0/me/onlineMeetings/{meeting-id}/recordings/{recording-id}
GET /microsoft-teams/v1.0/me/onlineMeetings/{meeting-id}/transcripts
Returns a list of transcripts for a meeting (available after the meeting has ended and transcription was enabled).
GET /microsoft-teams/v1.0/me/onlineMeetings/{meeting-id}/transcripts/{transcript-id}
GET /microsoft-teams/v1.0/me/onlineMeetings/{meeting-id}/attendanceReports
Returns attendance reports for a meeting (available after the meeting has ended).
GET /microsoft-teams/v1.0/me/onlineMeetings/{meeting-id}/attendanceReports/{report-id}
GET /microsoft-teams/v1.0/me/chats
GET /microsoft-teams/v1.0/chats/{chat-id}
GET /microsoft-teams/v1.0/chats/{chat-id}/messages
POST /microsoft-teams/v1.0/chats/{chat-id}/messages
Content-Type: application/json
{
"body": {
"content": "Hello in chat"
}
}
Microsoft Graph uses OData-style pagination with @odata.nextLink:
GET /microsoft-teams/v1.0/me/joinedTeams?$top=10
Response includes pagination link when more results exist:
{
"value": [...],
"@odata.nextLink": "https://graph.microsoft.com/v1.0/me/joinedTeams?$skiptoken=..."
}
Use the $top parameter to limit results per page.
$top=10 - Limit results$skip=20 - Skip results$select=id,displayName - Select specific fields$filter=membershipType eq 'private' - Filter results$orderby=displayName - Sort resultsconst response = await fetch(
'https://api.maton.ai/microsoft-teams/v1.0/me/joinedTeams',
{
headers: {
'Authorization': `Bearer ${process.env.MATON_API_KEY}`
}
}
);
const data = await response.json();
import os
import requests
response = requests.get(
'https://api.maton.ai/microsoft-teams/v1.0/me/joinedTeams',
headers={'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'}
)
data = response.json()
import os
import requests
team_id = "your-team-id"
channel_id = "your-channel-id"
response = requests.post(
f'https://api.maton.ai/microsoft-teams/v1.0/teams/{team_id}/channels/{channel_id}/messages',
headers={
'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}',
'Content-Type': 'application/json'
},
json={'body': {'content': 'Hello from Maton!'}}
)
data = response.json()
from.user field shows the actual user identityb643f103-870d-4f98-a23d-e6f164fae33e)19:9fwtZjo3IM0D8bLdQqR-_oMFw1eUDlzWjPfIhNGhVd41@thread.tacv2)1771359569239)text (default) or htmlstandard, private, sharedme endpoint is supported for listing joined teams (not arbitrary user IDs)curl -g when URLs contain brackets to disable glob parsingjq or other commands, environment variables like $MATON_API_KEY may not expand correctly in some shell environments| Status | Meaning |
|---|---|
| 400 | Missing Microsoft Teams connection or invalid request |
| 401 | Invalid or missing Maton API key |
| 403 | Insufficient permissions for the requested resource |
| 404 | Team, channel, or message not found |
| 429 | Rate limited (Microsoft Graph throttling) |
| 4xx/5xx | Passthrough error from Microsoft Graph API |