Have you thought about creating a private channel in Teams using a API? Yes, it is possible to do so in case you were wondering is that really supported or not. Using Graph Explorer is simple, fast and easy for this testing.
First you need to figure out a few IDs:
- Team ID (for example using GET https://graph.microsoft.com/v1.0/me/joinedTeams )
- User IDs you want to use (GET https://graph.microsoft.com/v1.0/users )
Creating a private channel with creator as owner
Create a POST call with following information. All POST calls are with Content-type of application/json
POST https://graph.microsoft.com/beta/teams/{teamID}/channels
{
"@odata.type": "#Microsoft.Graph.channel",
"membershipType": "private",
"displayName": "Private corner",
"description": "This is for private content"
}

Looking at Members-information (… menu and Manage channel) you can see the current user became the owner of the channel

Creating a private channel with two owners
This is very similar as before but we include a user block in the POST call. The actual call URL remains the same.
POST https://graph.microsoft.com/beta/teams/{teamID}/channels
{
"@odata.type": "#Microsoft.Graph.channel",
"membershipType": "private",
"displayName": "Private corner too",
"description": "This is for private content",
"members":
[
{
"@odata.type":"#microsoft.graph.aadUserConversationMember",
"user@odata.bind":"https://graph.microsoft.com/beta/users('{UserID1}')",
"roles":["owner"]
},
{
"@odata.type":"#microsoft.graph.aadUserConversationMember",
"user@odata.bind":"https://graph.microsoft.com/beta/users('{UserID2}')",
"roles":["owner"]
},
]
}
Now we have two owners in the channel.

Creating a private channel with a specific owner and adding a member
The last variation is to add owners and members when creating a private channel:
POST https://graph.microsoft.com/beta/teams/{teamID}/channels
{
"@odata.type": "#Microsoft.Graph.channel",
"membershipType": "private",
"displayName": "Private corner again",
"description": "This is for private content",
"members":
[
{
"@odata.type":"#microsoft.graph.aadUserConversationMember",
"user@odata.bind":"https://graph.microsoft.com/beta/users('{UserID1}')",
"roles":["owner"]
},
{
"@odata.type":"#microsoft.graph.aadUserConversationMember",
"user@odata.bind":"https://graph.microsoft.com/beta/users('{UserID2}')",
"roles":["member"]
},
]
}
And we can see Amy being a member on this channel.

As usual, read Docs.Microsoft.Com article about channel creation for details and updates. The API is in beta, so it may change.
Let’s use Power Automate (Flow) for creation
I needed to switch the environment here (yeah, licenses…) but using a Power Automate to create a channel based on a trigger isn’t difficult in case you have a provisioning process in place. Let’s assume that we have a list somewhere that will trigger the creation of a team and it will automatically create a private channel there. Simple POC case.

- Create a team using Graph API in a flow.
- Ensure you get the freshly created team id. The provisioning may take some time, so I have added a 15 second delay and then start listing teams to find out the created team id
- Create the POST uri call for Private channel creation (above in this post)
- I couldn’t get the @odata.type to work in a request call so I ended up putting it into a variable.. Fix for this: I was told that using double at-sign (@@odata.type) when entering information to the request body actually solves this one without a need to use variable workaround. You learn something new every day!
- Place the private channel call into request


Creation was started when a new item was added to the list. After Power Automate run successfully the team appeared with the private channel that has one owner (me) as defined in the call.



This is great.
Thanks!
LikeLike
Thank You!
LikeLike
Great post!
But I am having problems creating private channels as it runs into an exception:
channel cannot be null.
Parameter name: channel
Do you have an idea why this happens?
LikeLike
Hmm. Is it not saying channel.displayName can not be null? DisplayName is the channel’s name.
LikeLike
This is the body of the Rest Call:
{
“membershipType”: “private”,
“displayName”: “Channel 13.05.2020 110453”,
“description”: “Description of Channel 2”,
“email”: “”,
“members”: [
{
“user@@odata.bind”: “https://graph.microsoft.com/beta/users(‘xxxxxxxxxxx’)”,
“roles”: [
“owner”
],
“@odata.type”: “#microsoft.graph.aadUserConversationMember”
}
]
}
LikeLike
unfortunatelly no, the displayName is filled and if I remove the property membershipType: private it gets created
LikeLike
Addt he odata.type as well to channel.
{
“@odata.type”: “#Microsoft.Teams.Core.channel”,
“membershipType”: “private”,
“displayName”: “Channel 13.05.2020 110453”,
“description”: “Description of Channel 2”
}
That succeeded.
LikeLike
thank you for the quick reply, I tried with the odata.type as well:
{
“membershipType”: “private”,
“displayName”: “Channel 13.05.2020 111739”,
“description”: “Description of Channel 2”,
“members”: [
{
“user@@odata.bind”: “https://graph.microsoft.com/beta/users(‘xxxxxxxxxx’)”,
“roles”: [
“owner”
],
“@odata.type”: “#microsoft.graph.aadUserConversationMember”
}
],
“email”: “”,
“@odata.type”: “#Microsoft.Teams.Core.channel”
}
LikeLike
{
“membershipType”: “private”,
“displayName”: “Channel 13.05.2020 111739”,
“description”: “Description of Channel 2”,
“members”: [
{
“user@@odata.bind”: “https://graph.microsoft.com/beta/users(‘3606b4b9-44b5-434f-b525-69d63bcc5506’)”,
“roles”: [
“owner”
],
“@odata.type”: “#microsoft.graph.aadUserConversationMember”
}
],
“email”: “”,
“@odata.type”: “#Microsoft.Teams.Core.channel”
}
Even with adding this it runs into the same error.
If I remove the Part to add the members it runs into an error that a member has to be added to the channel… really strange
LikeLike
unfortunatelly this as well does not help
LikeLike
I found the solution:
within the member section the @odata.bind has to be first initialized in a seperate variable (like the @odata.type) and then added to the body of the call
Thank you for your support
LikeLiked by 1 person
I am passing the below request body..but error “Invalid user. Please verify the user@odata.bind is correct.”
{
“membershipType”: “private”,
“displayName”: “Private corner again1”,
“description”: “This is for private content”,
“members”: [
{
“roles”: [
“owner”
],
“user@odata.bind”: “https://graph.microsoft.com/v1.0/users/cfa4e116-b5ca-47f4-ba33-999587ed5bc2”,
“@odata.type”: “#microsoft.graph.aadUserConversationMember”
}
],
“email”: “”,
“@odata.type”: “#Microsoft.Teams.Core.channel”
}
LikeLike
I am getting this error….”Invalid user. Please verify the user@odata.bind is correct.”
{
“membershipType”: “private”,
“displayName”: “Private corner again1”,
“description”: “This is for private content”,
“members”: [
{
“roles”: [
“owner”
],
“user@odata.bind”: “https://graph.microsoft.com/v1.0/users/cfa4e116-b5ca-47f4-ba33-999587ed5bc2”,
“@odata.type”: “#microsoft.graph.aadUserConversationMember”
}
],
“@odata.type”: “#Microsoft.Teams.Core.channel”
}
LikeLike
The user uri is not wrong i have verified in graph explorer
LikeLike
Your format for userid is slightly wrong. You need to include the GUID inside (‘GUID’)
“user@odata.bind”: “https://graph.microsoft.com/beta/users/(‘userGUIDhere’)”,
like in the Docs example. https://docs.microsoft.com/en-us/graph/api/channel-post?view=graph-rest-beta&tabs=http
There seem to be also have some changes to permissions – remember to update those as well.
LikeLike
Thanks @Vesa it worked….
The problem being…
graph explorer API with formats
1. ../v1.0/users/id
2. ../v1.0/users(‘id’)
3 ../beta/users(‘id’)
4 ../beta/users/id
works fine directly for any userid
but here for the user@odata.bind…..its only accepting ./beta/users(‘id’) this syntax…cuz till now fortunately I tried only with other syntax’s….hehe.
But anyways..Thanks for your immediate help
LikeLiked by 1 person
I am glad you got it working!
Indeed – there is some work to be done with Graph API so it would work in a coherent way.
LikeLike
Hi Vesa,
I’ve tried many times without sucess and trying all advice in your page (the only one that I found with some details on the web).
This is my flow’s code:
{
“membershipType”: “private”,
“displayName”: “@{variables(‘privateChannelName’)}”,
“description”: “This is for private content”,
“members”:
[
{
“user@@odata.bind”:”https://graph.microsoft.com/beta/users(‘@{variables(‘SOID’)}’)”,
“roles”:[“owner”
],
“@{variables(‘odata.type’)}”:”#microsoft.graph.aadUserConversationMember”,}
]
}
The error:
{
“error”: {
“code”: “BadRequest”,
“message”: “channel cannot be null.\r\nParameter name: channel”,
“innerError”: {
“date”: “2020-06-15T11:08:55”,
“request-id”: “d589955a-f0a7-4da5-ae09-8fe8b47b0dcd”
}
}
}
Over and over…
I have also posted the issue in the Power Automate Community:
https://powerusers.microsoft.com/t5/Building-Flows/Issue-with-Create-Private-Channel-via-Power-Automate/m-p/592991#M78155
LikeLike
Chris had a similar issue before and he found an answer in @odata.bind.
https://myteamsday.com/2020/01/03/create-private-channel-graph-api/comment-page-1/#comment-1323
In my demos I create a private channel in a Flow so there is something in error with your code. Hopefully it is that @odata.bind and it will help your get forward. I see you have double at-signs in user@@odata.bind
(that is sometimes a bit tricky in Flow I have found out)
{
“membershipType”: “private”,
“displayName”: “@{variables(‘Private Channel name’)}”,
“description”: “This is a graph created private channel”,
“members”: [
{
“@odata.type”: “#microsoft.graph.aadUserConversationMember”,
“user@odata.bind”: “https://graph.microsoft.com/beta/users(‘@{body(‘Get_Requester_ID’)?[‘id’]}’)”,
“roles”: [
“owner”
]
}
]
}
LikeLiked by 1 person
Hi Vesa, actually as I went through the comments over and over I found that it works when both @odata.type and @odata.bind are previously initialized as variables. It was not easy though to get there…
LikeLike
I am glad you got it working. A good observation.
LikeLike
Hi Vesa,
Great blog again!
You happen to know how to list the private channels of a Microsoft eams Team??
When using the list channels (https://graph.microsoft.com/beta/teams/{GUID}/channels) it seems private channels are not included so maybe you know another way 🙏
LikeLike
That call does return you also private channels of a team – if you are a member or owner in those channels.
If you can use Microsoft Teams PowerShell beta then Get-TeamChannel returns also private channels created by others (assuming you are a team owner)
https://docs.microsoft.com/en-us/powershell/module/teams/Get-TeamChannel?view=teams-ps
If you haven’t seen this yet I suggest to take a look at this:
https://docs.microsoft.com/en-us/microsoftteams/private-channels-life-cycle-management
Thank you! 🙂
LikeLiked by 1 person
Problem is it will create hidden channel by default ,anyother way to create show channel .
LikeLike
How do I find out the user id?
LikeLike
If you have user email then you can use Get user Profile (V2) action in Power Automate for example.
LikeLike
Just add an extra @ like this:
{
“@@odata.type”: “#Microsoft.Teams.Core.channel”,
“membershipType”: “private”,
“displayName”: “Private corner too”,
“description”: “This is for private content”,
“members”:
[
{
“@@odata.type”:”#microsoft.graph.aadUserConversationMember”,
“user@odata.bind”:”https://graph.microsoft.com/beta/users(‘{UserID1}’)”,
“roles”:[“owner”]
},
{
“@@odata.type”:”#microsoft.graph.aadUserConversationMember”,
“user@odata.bind”:”https://graph.microsoft.com/beta/users(‘{UserID2}’)”,
“roles”:[“owner”]
},
]
}
LikeLike
Thank you Ola, a great tip! I think these days that trick works a lot better than earlier days.
LikeLike
Question:
When we create a variable “@odata.type”, why do we need that? We use it twice, for Microsoft channel & add conversation member, but when we initialize it, what should we feed in that?
LikeLike
I used that in Graph calls because I got an error in a Flow if I tried to type it in. Hopefully it won’t be needed anymore.
LikeLike
I started getting error this week with private channel creation.
I was passing @odata.type as “#Microsoft.Teams.Core.Channel”.
it started throwing error : – Invalid odata.type specified.
Then I checked their documentation looks like they changed that odata.type to “#Microsoft.Graph.Channel”
LikeLike
Thank you for the heads-up! I updated the article.
LikeLike
Yeah this breaking change will be a problem for everyone having used the old odata.type value. Is there anywhere Microsoft announces breaking changes to their API or did they just make a mistake here?
LikeLike
I think this one should have all changes in it: https://developer.microsoft.com/en-us/graph/changelog
However checking the conversation here ( https://github.com/microsoftgraph/microsoft-graph-docs/issues/7881 ) it looks like this change happened in July 2020 but without any notification to change logs. It might have been part of the rollout from beta to 1.0.
LikeLike
Great article – thanks!
I have one question though. And I cannot seem to find any answers on the wicked wide web!
how can I use the GraphAPI (in this case from powerautomate/logic app adapter) to add a GUEST member to a private channel?
Is tried this:
{
“roles”: [
“guest”
],
“user@odata.bind”: “https://graph.microsoft.com/v1.0/users(‘user_surname_guestdomain.com#EXT#@mydomain.onmicrosoft.com’)”,
“@@odata.type”: “#microsoft.graph.aadUserConversationMember”
}
And get this error:
“code”: “BadRequest”,
“message”: “Bad Request – Error in query syntax.”,
LikeLike
You need to first invite guests to the tenant and add them to the team.
After that you can add them to the Private Channel with their ID like this
POST https://graph.microsoft.com/beta/teams/{teamid}/channels/{privatechannelid}/members
{
“@odata.type”: “#microsoft.graph.aadUserConversationMember”,
“roles”: [
“member”
],
“user@odata.bind”: “https://graph.microsoft.com/v1.0/users(‘{guest user guid in AAD’)”
}
You can get the id by querying team members.
What is odd that the added user is not visible in channels member list in UI but can be atmentioned etc.
It is also returned with GET members for that channel call.
LikeLike
thanks for the article.
I am faced with a slightly different problem and cannot find an answer on the entire google-first-page-result no matter how i word my search.
I need to add GUEST accounts to a private teams channel with MS graph from within powerautomate/logicapps.
This is my JSON call:
{
“roles”: [
“guest”
],
“user@odata.bind”: “https://graph.microsoft.com/v1.0/users(’emailaddress_myguestdaomain.com#EXT#@teamsdomain.onmicrosoft.com’)”,
“@odata.type”: “#microsoft.graph.aadUserConversationMember”
}
and the error result:
“code”: “BadRequest”,
“message”: “Bad Request – Error in query syntax.”,
LikeLike
Awesome Post! Thank you very much, I will try it out!
LikeLike