Create a Teams Private Channel using Graph API and Power Automate (Flow)

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:

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.

  1. Create a team using Graph API in a flow.
  2. 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
  3. Create the POST uri call for Private channel creation (above in this post)
  4. 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!
  5. 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.


39 thoughts on “Create a Teams Private Channel using Graph API and Power Automate (Flow)

  1. 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?

    Like

      1. 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”
        }
        ]
        }

        Like

  2. unfortunatelly no, the displayName is filled and if I remove the property membershipType: private it gets created

    Like

    1. 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.

      Like

      1. 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”
        }

        Like

  3. {
    “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

    Like

  4. 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

    Liked by 1 person

  5. 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”
    }

    Like

  6. 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”
    }

    Like

      1. 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

        Liked by 1 person

      2. 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.

        Like

  7. 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

    Like

    1. 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”
      ]
      }
      ]
      }

      Liked by 1 person

      1. 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…

        Like

    1. 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! 🙂

      Liked by 1 person

  8. Problem is it will create hidden channel by default ,anyother way to create show channel .

    Like

  9. 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”]
    },

    ]
    }

    Like

  10. 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?

    Like

  11. 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”

    Like

    1. 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?

      Like

  12. 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.”,

    Like

    1. 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.

      Like

  13. 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.”,

    Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.