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.Teams.Core.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.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"]
        },

     ]
}

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


29 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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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