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 Flow
• Setup The Invoice Approvals SharePoint List
• Trigger The Approval Flow When A File Is Created
• Get The User Profile For Each Approver
• Make A Connection To Microsoft Teams Approvals
• Create A Multi Level Approval Using An HTTP Request
• Wait For The Multi Level Approval To Be Completed
• Record The Approval Outcome And Approval History In SharePoint
• Test The Multi Level Approval Flow
• Sample 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"
]
}
]
}
Did You Enjoy This Article? 😺
Subscribe to get new Power Apps & Power Automate articles sent to your inbox each week for FREE
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.
Thanks Matt! Great workflow here. I hope to implement this at some point.
James,
I’m glad you liked it. I think Power Automate should offer this as part of the standard approvals connector to make it easy for every builder.
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.
Beck D,
The Approvers do not require any premium license. But yes, the account which initiates the flow does.
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,
Happy to clarify. The Flow Owner is the account which requires the license 🙂
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)
How is this different or helps better from out of the box approval action?
Ramesh,
The out of the box approvals action does not support multi level approvals. The best it can do is multiple single level approvals. Which shows an inaccurate approvals history. Or everyone must approve style approvals which does not guarantee an order of responses.
This is a true multi level approval action. The other ways of doing it currently are not.
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
Krishna,
Try this fixes suggested by the forums and let me know if it works please:
https://powerusers.microsoft.com/t5/General-Power-Automate/powerautomate-quot-start-and-wait-for-approval-quot-gives-a-502/td-p/1727691
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!
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,
#1. There is a way to have approvals beyond 30 days without a flow timeout. I will publish it next week.
#2. I’m going to look into your cancellation questions. It might be possible to get this endpoint from Teams online. Otherwise, I think it’s likely to be included when Approvals gets added to the MS Graph API in June 2024.
I’m giddy, and… YAY!!!
Sent approvals can be canceled from within the approvals app in Teams. It has a tab for sent approvals by you and there you can cancel them as the requestor. Approvers will be notified immediately, the approval request can’t be approved or rejected by them anymore.
Hello Matt,
How is this different from a multi level TEAMS Approval process?
Ahkilesh,
It is exactly the same as the multi level Teams approvals process. You are now able to trigger it from Power Automate. There is not any standard flow action that does this.
And from what I’ve seen no one else has published how to do it in Power Automate yet. That is the point of my article.
the point.
Hey! Will this bypass the timeout issue in approvals? Or will the 30day timeout still be applicable?
Whitney,
The flow timeout is still 30 days but the approval lives on indefinitely. Next week I am planning to post an article on how to bypass limit for approvals. You will be able to have approvals of infinite length.
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,
I will be posting an article on how to make approvals with an infinite response time next week. Stay tuned!
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!
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.
Daniel,
Yes, it would appear to solve the problem you mentioned. The next approval is only sent after the previous one is complete. There will be less chances of two people viewing the same document at once this way.
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?
James,
You are in luck. The Approvals API is expected to be added to the MS Graph API in June 2024. I would suggest to wait another month before fully building out your solution. I’m fairly certain the CreateApproval endpoint does support custom response. It’s just a question of finding the time for me to investigate it vs. waiting for the graph API docs right now. For your final question, to trigger a flow as each incremental approval is created you would need to monitor updates to the approval request entity.
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?
Reynaldi,
Yes, you could do it for free by using the Office 365 Groups V1 connector. It is deprecated, but there are still workarounds to find it.
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?
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.
Dariuz,
I have updated my article with the information. Thank you for helping me realize I didn’t include that code yet!
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?
Amanda,
Yes, you could add approvers dynamically from a person field of a SharePoint list. Or any other datasource you choose.
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,
Which action gave you the error? Please send a screenshot of how you filled in the action and also the action showing the error message.
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 🙂
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!
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:
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!
Kevin De Jong,
Yes, my example was done within in the Default environment. I wonder if there is another parameter to provide for the environment name inside of Invoke An HTTP request…
Can you try adding this parameter to the triggery body to match your environment?
“FlowEnvironment”: “2f8a1d5d-7c34-40bd-901e-54693fdc94bd”
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!
Kevin,
My next articles will focus on how to cancel approvals and also reassign them. They are already written. Just waiting to publish.
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’]}”
]
}
]
}
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.
Dariuz,
Can you please help me to understand some more details behind your scenario?
– Which environment did you build the flow in? (I used default)
Can you try adding this parameter to the triggery body to match your environment?
“FlowEnvironment”: “2f8a1d5d-7c34-40bd-901e-54693fdc94bd”
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,
I am glad it solved the issue. Next I plan to publish how to Cancel an approval and how to Reassign and approval using the teams approvals API.
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
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!
Iain,
Yes, I saw it in the roadmap and was very unhappy that no progress was made on the feature. It is what drove me to figure it out for myself.
I believe the reason we did not get the feature yet could be two things:
1. Approvals are being added to the Graph API by June 2024 so creating a sequential approvals action before this seems like bad timing.
2. Copilot investments were prioritized at the expense of other products.
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.
Thank you!
Paul,
1. I believe you could generate the Approval steps properly dynamically based upon an array of approvers and use a single flow.
2. I would create a scheduled flow that checks the Approval Request table for non-completed records made X days 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,
Sorry, but I don’t have a rundown available for this. But yes, you could take an array of approval and use Power Automate to format it for the Steps portion of the code above.
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?
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.
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?
Emil,
We could potentially use a “custom options” style of approval here. But I have not mapped that out yet.
The workflow cannot be restarted from the rejected step ownwards. But a rejected approval can be rejected to previous as opposed to flat out cancellation.
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.