The Hidden Multi Level Approval Feature in Power Automate

The Hidden Multi Level Approval Feature in Power Automate

A multi level approval in Power Automate can be used to send an approval request to a 1st level approver, 2nd level approver and more. The approval action returns a final outcome of either accepted or rejected and a full history of each approvers response with a timestamp. The Approvals connector does not include a standard action for sequential approvals. However, we can use the Invoke an HTTP Request action to create a multi level approval in a Power Automate flow.

Table of Contents
• Introduction: The Multi Level Invoice Approval FlowSetup The Invoice Approvals SharePoint ListTrigger The Approval Flow When A File Is CreatedGet The User Profile For Each ApproverMake A Connection To Microsoft Teams ApprovalsCreate A Multi Level Approval Using An HTTP RequestWait For The Multi Level Approval To Be CompletedRecord The Approval Outcome And Approval History In SharePointTest The Multi Level Approval FlowSample Code: Create An Approval HTTP Request




Introduction: The Multi Level Invoice Approval Flow

Employees at a construction company use a mutli level approval process to approve vendor invoices for payment. A Power Automate flow sends the 1st level approval request to a Project Coordinator and the 2nd level approval request to the Project Manager.



The multi level approval status is tracked in a SharePoint document library. A full history of the approvals process is recorded including request and responses dates.




Setup The Invoice Approvals SharePoint List

Create a new SharePoint document library to store invoices, track the invoice status and the approval history with the following columns:

  • Name
  • Invoice Status (choices: Submitted, Approved, Rejected)
  • Approval History (multiple lines of text)




Trigger The Approval Flow When A File Is Created

Open make.powerautomate.com and create a new automated flow. Select the SharePoint – When A File Is Created (Properties Only) trigger and target the Invoice Approvals document library.



Once the approvals flow begins we want to mark the document with a status of Submitted to indicate the approvals process has started. Add a SharePoint – Update File Properties action, use the ID from the trigger and set the Invoice Status Value to Submitted.




Get The User Profile For Each Approver

Every user in Office 365 is assigned unique identifier. We must obtain the ID for each approver in order to send them an approval.

Create an Office 365 Users – Get User Profile action and input the email address of the 1st level approver. Then insert a 2nd Office 365 Users – Get User Profile action and supply the email address of the 2nd level approver. These actions will return the full user profile for each approver including their ID, display name, job title, location and much more.




Make A Connection To Microsoft Teams Approvals

A multi level approval is not available as part of the Microsoft Teams actions included in Power Automate. We will use the HTTP With Microsoft Entra ID (Pre-authorized) – Invoke An HTTP Request action to create an approval instead.

Upon adding the action we will be asked to setup a connection.





Use the following URL in both the Base Resource URL and Microsoft Entra ID Resource URI fields and press the Sign In button.

https://approvals.teams.microsoft.com




Create A Multi Level Approval Using An HTTP Request

Once the connection is setup we can configure the Invoke An HTTP Request action to create an approval.



Select the following method to indicate the HTTP request will create approvals records.

POST



Use this URI to access the create approvals endpoint.

/api/CreateApproval



Include an Accept header and use this code in the value field

application/json



Then copy and paste this code into the body of the Invoke An HTTP Request action. Make to rename actions added to the flow so far match the screenshots in this tutorial to ensure the dynamic values do not result in an error.

The Steps property defines who should receive the approval and which order they should receive it in. This example only included two approvals but more can be added using the same object structure.

TextAttachments stores the location of the document to be approved in the NoteText field along with a display name in the Subject property.

{
  "Title": "Invoice Approval",
  "Details": "An invoice is ready for your approval.",
  "ApprovalType": 0,
  "Type": 5,
  "Creator": 0,
  "AssignedTo": [],
  "ApproverNames": [],
  "TextAttachments": [
    {
      "Subject": "@{triggerOutputs()?['body/{FilenameWithExtension}']}",
      "NoteText": "@{triggerOutputs()?['body/{Link}']}"
    }
  ],
  "Steps": [
    {
      "AssignedTo": [
        "@{outputs('Get_user_profile_(V2):_Approver_1')?['body/id']}"
      ],
      "ApprovalPolicy": 1,
      "ApproverNames": [
         "@{outputs('Get_user_profile_(V2):_Approver_1')?['body/displayName']}"
      ]
    },
    {
      "AssignedTo": [
        "@{outputs('Get_user_profile_(V2):_Approver_2')?['body/id']}"
      ],
      "ApprovalPolicy": 1,
      "ApproverNames": [
         "@{outputs('Get_user_profile_(V2):_Approver_2')?['body/displayName']}"
      ]
    }
  ],
  "FlowEnvironment": "@{workflow()?['tags']?['environmentName']}"
}




Wait For The Multi Level Approval To Be Completed

An multi level approval flow becomes completed when all approvers in the sequence have responded with “Approve” or any single approver responded with “Reject.”



Add an Approvals – Wait For An Approval action and use this expression to fill-in the Approval ID field.

body('Invoke_An_HTTP_request:_Create_Approval')?['ApprovalId']



Then add a Data Operations – Compose action and write this expression to determine whether an approvals flow had an outcome of Approve or Reject.

trim(last(split(body('Wait_for_an_approval')?['outcome'],',')))



The outcome of an approval is returned as comma separated text. We must use the split, last and trim functions to get the final value without any whitespace.




Record The Approval Outcome And Approval History In SharePoint

The approval outcome and approvals history should be stored in SharePoint when the approvals process is finished. Add a new Condition to the flow where the Compose action is equal to Approve. Then insert a SharePoint – Update File Properties action to the If Yes block and set the Invoice Status Value to Approved. Do the same for the If No block but change the Invoice Status Value to Rejected.



Use this code in the Approval History field for both SharePoint – Update File Properties actions to record the approvals history.

body('Wait_for_an_approval')?['responseSummary']




Test The Multi Level Approval Flow

We are now finished building the multi level approval flow. Save and test the flow to ensure it works.



The 1st level approval response is sent to the Project Coordinator named Mary Baker.



When Mary approves the invoice the 2nd level approval is sent to the Project Manager Matthew Devaney.



Once Matthew approves the invoice status is set to approved and the approval history is recorded alongside the document in SharePoint.




Sample Code: Create An Approval HTTP Request

For reference, here is the HTTP request body needed to create the multi level approval showing what the dynamic values evaluate to. This code can be modified to add more approval levels if you require it.



Sample code:

{
  "Title": "Invoice Approval",
  "Details": "An invoice is ready for your approval.",
  "ApprovalType": 0,
  "Type": 5,
  "Creator": 0,
  "AssignedTo": [],
  "ApproverNames": [],
  "TextAttachments": [
    {
      "Subject": "Adatum 1.pdf",
      "NoteText": "https://matthewdevaney.sharepoint.com/sites/MatthewDevaneyBlog/Invoice%20Approvals/Adatum%201.pdf"
    }
  ],
  "Steps": [
    {
      "AssignedTo": [
        "34a4e2c8-6020-4421-92ca-e04a4a29edfe"
      ],
      "ApprovalPolicy": 1,
      "ApproverNames": [
         "Mary Baker"
      ]
    },
    {
      "AssignedTo": [
        "6857d910-10c3-485e-a492-6456ce2f1625"
      ],
      "ApprovalPolicy": 1,
      "ApproverNames": [
         "Matthew Devaney"
      ]
    }
  ]
}




Questions?

If you have any questions or feedback about The Hidden Multi Level Approval Feature In Power Automate please leave a message in the comments section below. You can post using your email address and are not required to create an account to join the discussion.

Matthew Devaney

Subscribe
Notify of
guest

61 Comments
Oldest
Newest
Inline Feedbacks
View all comments
James W
James W
1 month ago

Thanks Matt! Great workflow here. I hope to implement this at some point.

Beck D
Beck D
1 month ago

Just checking is the “Invoke a HTTP request” is a premium action? If so, I’m assuming there is no way to do this without having premium licensing.

Paul
Paul
1 month ago

Hello. I am looking at possibly using this for a document approval workflow. When you state the “account which initiates the flow”, would that be the user that is click our “send for approval” button on SP, or the user that created the flow?

Paul
Paul
1 month ago

Awesome thank you! Just ensuring I dont populate and setup the flow, and then require all of my users to need premium access (when 98% will just click submit or approve)

Ramesh Mukka
Ramesh Mukka
1 month ago

How is this different or helps better from out of the box approval action?

krishna
krishna
1 month ago

hi matt, there is a wait for an approval action requires Approval ID how to fetch approval ID i am getting error as bad gateway

Scott
Scott
1 month ago

Thanks for this Matthew.

I’m currently working on a Power App that uses the HTTP With Microsoft Entra ID (Pre-authorized) connector but I’m really struggling to get it working. I was wondering if you could share where you sourced any documentation for it when building out this solution, as the documentation I have found so far is severely lacking.

Thanks!

Doug Booth
Doug Booth
1 month ago

Matt, as always, this is brilliant and beautiful!
The real problem I have with the out-of-the-box MS solutions for approvals is that, once they are spawned, we have no control over them. I mean, they can’t be cancelled. There’s a maximum timeout of something like 30 days, I think, but my timing is limited to how long I want to wait for a response. If my business requires a 4 day wait period and then we move on, I have no way of cancelling that approval I sent out. From reading I’ve done, the only way to do this is an ugly “back door” solution, deleting the Dataverse record itself. I’m on a government tenant, and I don’t have that access, and believe it’s a dangerous solution in and of itself. There should be a cancel action that can be taken, notifying the approvers that they took too long and there’s nothing more they can do. Currently, if they ignore my email notification that time’s up, they can still approve or deny, but the Flow is no longer listening. This leaves lots of room for confusion and disparity. I welcome any suggestions you might have for this. In the meantime, I’m considering building my own approvals system in SPO and Power Automate with the features my org needs.

Doug Booth
Doug Booth
1 month ago

I’m giddy, and… YAY!!!

Akhilesh
Akhilesh
1 month ago

Hello Matt,

How is this different from a multi level TEAMS Approval process?

Whitney
Whitney
1 month ago

Hey! Will this bypass the timeout issue in approvals? Or will the 30day timeout still be applicable?

Scott
Scott
1 month ago

Much appreciated definitely going to look into incorporating this!

Question for you, though – I’m trying to think through this and see if there is any way to account for a timeout? Some of the scenarios in my environment end up having approvers that don’t respond with in the allotted 30 days for a timeout and I have to retrigger the flow.

Is there any way to create a separate column that can be used as a reference to create a new HTTP request based on a timeout? Or at least use it as a switch statement essentially?

Scott
Scott
1 month ago

Much appreciated! I currently have logic built in for the time outs and re-triggering based off a specific “Flow status” column through a switch statement.

Looking forward to seeing what you post!

Daniel J
Daniel J
1 month ago

Hello, thanks for another great article.
I have a Flow with multiple approvals all run at the same time in parallel, but we have issues when one user approves and the document is open for review by another approver.
This looks like it might just solve our problem – do you know if this would be the case?
Thanks, Daniel.

James M
James M
1 month ago

Love this! Wish these hidden APIs were publicly documented. I’m in the process of building a multi-step approval orchestration service that is a thin wrapper around MS Approvals and this may greatly reduce the amount of orchestration logic I need to apply. Any idea whether this CreateApproval endpoint supports custom responses? I have a use-case where the potential responses need to be a list of options rather than just ‘Approve’ / ‘Reject’ which I am able to do using the Create an Approval action in Power Automate.

Also, do you know if there is a way to track incremental approvals? For example, if I wanted to be able to trigger a cloud flow as each approval step is completed, could I trigger on update to the Approval entity, or would I need to go based on the Approval Request/Response entities?

Reynaldi Karundeng
Reynaldi Karundeng
1 month ago

Hi Matthew, does it mean that when added to graph API, we can use this for free if we run the HTTP request from powerapps using Office 365 groups connectors to post the Approval request, and gain the approval Id to be put on power automate?

James M
James M
14 days ago

Hey just following up on this-finally got an opportunity to set it up and it appears to be working really well! Only issue I have is that the requestor seems to get lost if the flow uses a service account’s Entra ID for making the API request. Also, I guess MS missed the June ’24 timeframe since I don’t see any reference added in MS Graph API docs.

I’m wondering if the “Creator” property in the request is the key here, but I’m not sure what the value should be. I tried changing 0 to the user’s Entra ID, but got an error from the API. Any ideas @Matthew?

Dariusz Kowalski
Dariusz Kowalski
1 month ago

Hello,
I am trying to reproduce the workflow but in the action “Wait for an approval” I cannot find the ApprovalId field – will you be able to prompt me what I could do wrongly?

Thank you in advance for any support.

problem.png
Amanda
Amanda
1 month ago

Hi Matt,
Thanks for posting this – it’s great timing, i’m trying to build an approval flow for quarterly reporting with multiple approvers and i’ve come across this! Yay!
I’ve a couple questions re: trying to apply this example to my scenario..

Would this flow work if the approvers weren’t hardcoded in? I was thinking of using the Person column for users to enter their approvers, and then the dynamic content for the approvers’ email addresses.
Our approvers would need to digitally sign the file submitted. I was thinking of combining this flow together with the actions of solution 1 outlined in your other post for locked files to prevent the file being locked for editing as it progresses through our 3 tier approval process (https://www.matthewdevaney.com/4-solutions-for-excel-file-is-locked-error-in-power-automate/). Any tips or insight on how to do so without overcomplicating the flow?

Last edited 1 month ago by Amanda
Julien S
Julien S
1 month ago

Hi Matthew, thanks for this very interesting post. I just tried to implement the flow and I get a error message : The response is not in a JSON format / InnerError : 401 Unauthorized. It seems that my JSON is fine. Did you had the same issue in one of your testings ?

Julien S
Julien S
1 month ago

It’s the Invoke a HTTP request from HTTP with Microsoft Entra ID (preauthorized) connector wich is in error. I simply copy/paste your code and replaced the dynamic values

Here is the body :

{
 “Title”: “Invoice Approval”,
 “Details”: “An invoice is ready for your approval.”,
 “ApprovalType”: 0,
 “Type”: 5,
 “Creator”: 0,
 “AssignedTo”: [],
 “ApproverNames”: [],
 “TextAttachments”: [
  {
   “Subject”: “@{body(‘Get_file_properties’)?[‘{FilenameWithExtension}’]}”,
   “NoteText”: “@{body(‘Get_file_properties’)?[‘{Link}’]}”
  }
 ],
 “Steps”: [
  {
   “AssignedTo”: [
    “@{outputs(‘Get_user_profile_(V2)’)?[‘body/id’]}”
   ],
   “ApprovalPolicy”: 1,
   “ApproverNames”: [
     “@{outputs(‘Get_user_profile_(V2)’)?[‘body/displayName’]}”
   ]
  },
  {
   “AssignedTo”: [
    “@{outputs(‘Get_user_profile_(V2)’)?[‘body/id’]}”
   ],
   “ApprovalPolicy”: 1,
   “ApproverNames”: [
     “@{outputs(‘Get_user_profile_(V2)’)?[‘body/displayName’]}”
   ]
  }
 ]
}

The same here after an execution. I just see that the URL include a ?d parameter which was not expected

{
 “Title”: “Invoice Approval”,
 “Details”: “An invoice is ready for your approval.”,
 “ApprovalType”: 0,
 “Type”: 5,
 “Creator”: 0,
 “AssignedTo”: [],
 “ApproverNames”: [],
 “TextAttachments”: [
  {
   “Subject”: “Document_RevolutionFrancaise_V2.docx”,
   “NoteText”: “https://MYTENANT.sharepoint.com/sites/PowerPlatformPlaygroundyj/Shared%20Documents/Document_RevolutionFrancaise_V2.docx?d=wa4bccda4224c4d77aa6354105b195e37”
  }
 ],
 “Steps”: [
  {
   “AssignedTo”: [
    “c97e8c99-f5cc-4891-9b32-41c5ad0a3364”
   ],
   “ApprovalPolicy”: 1,
   “ApproverNames”: [
     “SEGUIN Julien (renexter)”
   ]
  },
  {
   “AssignedTo”: [
    “c97e8c99-f5cc-4891-9b32-41c5ad0a3364”
   ],
   “ApprovalPolicy”: 1,
   “ApproverNames”: [
     “SEGUIN Julien (renexter)”
   ]
  }
 ]
}

Finally I’m wondering if it could come from the copy/paste action as I have lot of \n and \” within the parameter while looking at an execution (see attached file)

let me know if you see something, but don’t bother too much if not 🙂

Capture d
Julio
Julio
1 month ago

Hi Matthew. Thanks a lot for you article! I would like to know if, when creating an Approval via HTTP Request, apart from the approval in Teams, will the user receive an email as he does when using the standard method? So he can approve/reject as well from the email.
Thanks again!

Kevin de Jong
Kevin de Jong
1 month ago

Hi Matthew,

I haven’t seen someone else posting about this so I’ll give it a go:

When I try the “Wait for an Approval” step I receive the the following error:

Error code: ‘ApprovalNotFoundOrNotAccessible’. Error Message: ‘The requested approval is not found or not accessible to the caller.’.

I’m working in a environment made for proof of concepts. Looking at the approvals in Teams, they seem to have been created in the default environment.

My guess is the approvals connector is bound to approvals in Dataverse table in the current environment. Which means It can’t look them up cross-environment.

Did you create your flow in the default environment?

I’m guessing using the “When a row is updated in a selected environment” trigger could offer a solution. I’m interested in what you think!

Edit: Using the Get Row by ID from selected environment I’m able to retrieve the Approval details from the default environment. Given the Approval ID is returned by the HTTP request some solution for cross-tenant usage of your multi-step approval solution should be possible!

Last edited 1 month ago by Kevin de Jong
Kevin de Jong
Kevin de Jong
1 month ago

Hey Matthew,

This works like a charm!
Looking forward to the Graph API doc to see what else we can build using the Approvals API.

Thank you!

Dariusz Kowalski
Dariusz Kowalski
1 month ago

Hello,
did anybody successfully run the full flow?

In my case flow is running smoothly up to first approval, but after the second approval is not taking place and flow is stopping with 502 Bad gateway error (please see screenshot).

Body of my Invoke an HTTP request action:

{
 “Title”: “Multilevel Approval”,
 “Details”: “Document is ready for your approval.”,
 “ApprovalType”: 0,
 “Type”: 5,
 “Creator”: 0,
 “AssignedTo”: [],
 “ApproverNames”: [],
 “TextAttachments”: [
  {
   “Subject”: “@{triggerBody()?[‘{FilenameWithExtension}’]}”,
   “NoteText”: “@{triggerBody()?[‘{Link}’]}”
  }
 ],
 “Steps”: [
  {
   “AssignedTo”: [
    “@{outputs(‘Get_user_profile_(V2)_Approver_1’)?[‘body/id’]}”
   ],
   “ApprovalPolicy”: 1,
   “ApproverNames”: [
     “@{outputs(‘Get_user_profile_(V2)_Approver_1’)?[‘body/displayName’]}”
   ]
  },
  {
   “AssignedTo”: [
    “@{outputs(‘Get_user_profile_(V2)_Approver_2’)?[‘body/id’]}”
   ],
   “ApprovalPolicy”: 1,
   “ApproverNames”: [
     “@{outputs(‘Get_user_profile_(V2)_Approver_2’)?[‘body/displayName’]}”
   ]
  }
 ]
}

Bad gateway 502.png
Dariusz Kowalski
Dariusz Kowalski
1 month ago

After further investigation I think that there is a problem with timeout of Invoke an HTTP request action (by default timeout is set to 120 seconds). @Matthew do you have an idea how to overcome this limitation? I tried to use “standard” HTTP request action but there is a problem with simple Entra user authentication.

timeout.png
Dariusz Kowalski
Dariusz Kowalski
1 month ago

Hi, indeed I was using user specific environment, not the default organization environment.

I checked and following part in request body is resolving the issue:

“FlowEnvironment”: “@{workflow()?[‘tags’]?[‘environmentName’]}”

Thank you very much for support 🙂

Dariusz Kowalski
Dariusz Kowalski
1 month ago

Matt can you prompt us the approval api documentation?
I want to change the approval requestor name (in your scenario is the service user on which approval is working, not actually the user which upload the file for an approval) but I cannot find the proper API doc

Last edited 1 month ago by Dariusz Kowalski
Iain Ray
Iain Ray
1 month ago

Hi Mathew, firstly thank you for reverse engineering this, if you look on the roadmap for power automate, serial approvals were in it and were supposed to of released May last year.
 
I think a team at Microsoft had been working on this to implement as per the roadmap, hence the new columns to support it, but another team (Power CAT) had also been working on Approvals Kit, at some point they realised, crap! we don’t need both. Maybe Approvals kit won out due to having the calendar and Workdays based timeout and Out of office features already integrated, I don’t know but at some point they downed tools on this to let approval kit fail.

Hopefully they will see sense, and add the functionality as promised to the power automate actions!

Paul
Paul
1 month ago

Hello!

I’m looking at implementing this for our document approval, but there a few items I wanted to validate before I start the implementation.

  1. The number of approvers we have vary by document (based on department and housed in a SharePoint List). We could have as few as 2, or as many as 5 depending on the document. My guess is that I would need to split this for each approver count (IE, recreate the above for 2, 3, 4 and 5 approver scenarios) to avoid “null” approvers?
  2. Is it possible to generate a reminder for the active approval only? IE, if the 2nd person in a 3 person approval is still working through their approval, is there a way to trigger a reminder email for just that person to complete their approval?

Thank you!

Paul
Paul
1 month ago

Thank you!

A follow up on number 1. Do you happen to have a rundown on the array part? I’m guessing i would generate the array to be in the code format for the “Steps” portion of the code above

Paul
Paul
1 month ago

Thanks again for the reply. I think I have one final question.

Is it possible to show in the approver comments a running history of the approval. For example, if 5 people are in the approval chain, and persons 1 and 2 have approved, and its currently pending person3, can it show:
Person1- Approved
Person2- Approved
Person3- Pending?

Eugene Harley
Eugene Harley
6 days ago
Reply to  Paul

Hi Paul,

I am going down this same path and wanted to let you know there is an Approval Step table as well that you can monitor. And as users approve you can update the item in SharePoint.

I am planning to use a column to show the remaining approvers.

Emiliano
Emiliano
1 month ago

Excellent post. I’d like to ask if it’s feasible to add other options to the workflow besides approve and reject.
Lastly, in case of rejection due to lack of information, can the workflow be restarted from the rejected step onwards?

Eugene Harley
Eugene Harley
6 days ago

Hi Matthew,

I just thought I would save you time and let you know that once you have enabled Sequential Approvers, you can no longer use ‘Custom Response Options’.

You can test this in Teams by making a sequential approval, you will notice the ‘Custom Response’ option disappears once sequential approval is selected.