List private team channel members with Microsoft Graph API in Microsoft Teams

My application for Microsoft Teams works as a tab and needs to get the list of the chat members or channel members, depending on where it is installed.

/chats/{chat-id}/members

/teams/{team-id}/channels/{channel-id}/members

To use the APIs, I get the Context object from the client SDK library to get the identifiers required for the APIs. It works fine for chats–both group and one on one (using Context.chatId), and public team channels (using Context.groupId and Context.channelId).

However, nothing I try seems to work for private team channels.

The context object returned for private team channels contains teamId and channelId, but they are equal, and using one value for both ids naturally doesn’t work. Here is an example of what is returned for a private team channel by the SDK library 1.11.0 (the latest):

{
  "locale": "en-us",
  "theme": "default",
  "subEntityId": "",
  "isFullScreen": false,
  "sessionId": "5194fd2b-5c9a-16a7-7411-94ddabffffff",
  "chatId": "",
  "meetingId": "",
  "parentMessageId": "",
  "hostClientType": "desktop",
  "tenantSKU": "unknown",
  "jsonTabUrl": "microsoft-teams-json-tab.azurewebsites.net",
  "userLicenseType": "Unknown",
  "appSessionId": "7503c11c-d524-409c-b58b-004810ffffff",
  "appLaunchId": "c736c663-cc0b-47c3-8824-ba56b7ffffff",
  "isMultiWindow": false,
  "appIconPosition": 79,
  "userClickTime": 1637007245298,
  "sourceOrigin": null,
  "userFileOpenPreference": "inline",
  "osLocaleInfo": {
    "platform": "macos",
    "regionalFormat": "en-gb",
    "longDate": "d MMMM y",
    "shortDate": "dd/MM/y",
    "longTime": "HH:mm:ss z",
    "shortTime": "HH:mm"
  },
  "frameContext": "settings",
  "isTeamArchived": false,
  "teamType": 0,
  "userTeamRole": 0,
  "channelRelativeUrl": "/sites/ffffff/Shared Documents/Devel",
  "channelId": "19:0bc109b412d9448bb6b1b3d4d485700b@thread.tacv2",
  "channelName": "Devel",
  "channelType": "Private",
  "defaultOneNoteSectionId": "",
  "teamId": "19:0bc109b412d9448bb6b1b3d4d485700b@thread.tacv2",
  "teamName": "Devel",
  "teamSiteUrl": "https://ffffff.sharepoint.com/sites/worldrtech-Devel",
  "teamSiteDomain": "ffffff.sharepoint.com",
  "teamSitePath": "/sites/ffffff",
  "teamTemplateId": "",
  "teamSiteId": "",
  "ringId": "general",
  "tid": "d158bb9f-f90c-422d-9d0d-0040efffffff",
  "loginHint": "ffffff@ffffff.uk",
  "upn": "nox@worldr.co.uk",
  "userPrincipalName": "ffffff@ffffff.uk",
  "userObjectId": "fc5a4a6d-60e2-4370-83bd-aab1baffffff"
}

You can see above that the two are equal:

"channelId": "19:0bc109b412d9448bb6b1b3d4d485700b@thread.tacv2"
"teamId": "19:0bc109b412d9448bb6b1b3d4d485700b@thread.tacv2"

I wonder, whether this is an expected behaviour, or something is broken there… 🤔 As per comment by @Prasad-MSFT, this is normal behaviour for private channels.

There is an answer suggesting that one should first list all the teams the user joined. However, I don’t see how I would connect this information to the context data shown above.

Is there a way to list the members of a private team channel? What am I missing?

UPDATE1 16.11:

I did an experiment, but the results got me puzzled. I followed the idea of getting all the teams of the user first. This got me ids of all the teams the user is a member of. I then requested members of for the current private channel for every team: I expected to get errors for all teams but one–that team the channel really belongs to. However, I got members for every request! That’s very confusing.

  1. /me/joinedTeams
  2. /teams/{id}/channels/{channel_id}/members for each team received in 1. and channelId received from context.
  3. Each call returned some members, which I did not expect…

UPDATE2 16.11:

This long-winded way gets me channel members in the end:

  1. /me/joinedTeams
  2. /teams/{id}/channels for each team received in 1.
  3. Find the channel with id matching the channelId from my context among those received in 2.
  4. /teams/{id}/channels/{channel_id}/members for the channel found in 3.

Looks like a lot of effort for such a simple thing. 🙄

Answer

This long-winded way gets me channel members list in the end. Only applies to private team channels because we don’t have group id/team id in the Context there. As per @Prasad-MSFT’s comment to the original post, there is no other way at the time of writing.

  1. Get /me/joinedTeams
  2. Get /teams/{id}/channels for each team received in #1.
  3. Find the channel with id matching the channelId from the Context among those received in #2.
  4. Get /teams/{id}/channels/{channel_id}/members for the channel found in #3.

Here is a caveat: if you create a private team channel and immediately add your tab to it, there is a chance that your new channel will not be returned by the API. If #3 in the above list of procedures fails, I ask the user to retry in a couple of minutes.