Refresh All Power BI Dataflows with One Power Automate Button
- MirVel

- 11 minutes ago
- 9 min read
Introduction
Refreshing one Power BI dataflow is easy. Refreshing every relevant dataflow across a tenant is repetitive, inconsistent, and easy to forget. This tutorial builds one manually triggered Power Automate cloud flow that discovers Dataflows Gen1 in active workspaces, submits each refresh through the Power BI REST API, supports large tenants through pagination, and records which requests succeeded or failed. One button replaces a long tour through the Power BI Service.
Scope: this solution targets Power BI Dataflows Gen1. Fabric Dataflow Gen2 uses different Fabric APIs. In this guide, active dataflows means dataflows returned inside workspaces whose tenant state is Active; the API does not expose a separate active flag on each dataflow.

Why This Matters
A tenant can contain dataflows in many workspaces owned by different teams. Manual refreshes then become a checklist: open a workspace, locate the dataflow, select refresh, move to the next workspace, and repeat. The larger the estate, the more likely someone misses a workspace or refreshes only part of the chain.
A central instant flow provides a consistent entry point for maintenance, month-end processing, testing after gateway changes, or recovery after an upstream source outage. It also gives you one run history in Power Automate. This solution submits refresh requests; it does not wait until every refresh finishes. If downstream dataflows depend on upstream dataflows, use a dependency-aware design and monitor completion before starting the next layer.
Prerequisites
A Power Automate Premium license, because HTTP with Microsoft Entra ID is a Premium connector.
A Fabric administrator account, or an approved service-principal design, to read tenant workspace metadata.
Tenant.Read.All or Tenant.ReadWrite.All for the delegated admin call.
Dataflow.ReadWrite.All and sufficient permissions in every target workspace for the refresh call.
Power BI Dataflows Gen1 with valid credentials and gateways already configured.
The permission split is important. The admin endpoint can list tenant metadata, but that read access does not automatically grant permission to refresh a dataflow. The connection identity must also be allowed to write to each target workspace. Test one refresh call before scaling the flow across the tenant.

Step-by-Step Solution
Important: rename actions exactly as shown below before inserting expressions. This prevents broken references.
1. Test your working connection
Use your existing HTTP action temporarily:
Rename: TestPowerBIConnection
Method: GET
URL:
/v1.0/myorg/admin/groups?$top=1Headers: leave empty
Body: leave empty
Save and test the flow.
Expected result:
Status code: 200The response should contain something similar to:
{
"value": [
{
"id": "workspace-guid",
"name": "Workspace name",
"state": "Active"
}
]
}After this works, delete the temporary HTTP action. Deleting the action does not delete the connection.
The admin endpoint requires the signed-in user to be a Fabric administrator and use Tenant.Read.All or Tenant.ReadWrite.All. Microsoft: Get Groups as Admin
2. Configure the manual trigger
Keep:
Manually trigger a flowOpen its Settings:
Concurrency control: On
Degree of parallelism: 1
This prevents two users from starting the complete tenant refresh simultaneously.
3. Initialize the variables

Directly after the trigger, add six separate Initialize variable actions.
Variable name | Type | Initial value |
PageSize | Integer | 1000 |
Skip | Integer | 0 |
MorePages | Boolean | true |
SubmittedCount | Integer | 0 |
FailedCount | Integer | 0 |
RefreshLog | Array | Expression below |
For RefreshLog, select the Expression tab and enter:
json('[]')Do not enter "[]" as text, because that creates a string instead of an array.
Your flow should now look like:
Manually trigger a flow
Initialize PageSize
Initialize Skip
Initialize MorePages
Initialize SubmittedCount
Initialize FailedCount
Initialize RefreshLog
4. Add the pagination loop
After the variables, add:
Control → Do untilRename it:
DoUntilNoMorePagesConfigure the stopping condition:
Left side: variable MorePages
Operator: is equal to
Right side: false
The loop starts because MorePages is initially true. It stops after a page contains fewer than 1,000 workspaces.
In the Do until settings, you can use:
Count: 100
Timeout: PT12H
The count refers to pages, not individual dataflows.
5. Retrieve active workspaces and dataflows
Inside DoUntilNoMorePages, add:
HTTP with Microsoft Entra ID (preauthorized)
→ Invoke an HTTP requestRename it:
GetActiveWorkspacesConfigure:
Method: GET
URL: select Expression and paste:
concat(
'/v1.0/myorg/admin/groups?$expand=dataflows&$filter=state%20eq%20''Active''&$top=',
string(variables('PageSize')),
'&$skip=',
string(variables('Skip'))
)Add this optional header:
Header | Value |
Accept | application/json |
Leave Body empty.
The resulting first request will effectively be:
The API supports $expand=dataflows, $top up to 5,000, and $skip for pagination. Microsoft limits this admin operation to 50 requests per hour or 15 per minute per tenant. Microsoft admin API documentation

6. Parse the API response
The post skips this action. Although direct body expressions sometimes work, explicit parsing makes the flow more reliable and provides proper dynamic fields.
Immediately below GetActiveWorkspaces, still inside the Do until, add:
Data Operations → Parse JSONRename it:
ParseWorkspaceResponseFor Content, select the Body output from GetActiveWorkspaces, or use:
body('GetActiveWorkspaces')Use this schema:
{
"type": "object",
"properties": {
"value": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": [
"string",
"null"
]
},
"state": {
"type": [
"string",
"null"
]
},
"dataflows": {
"type": [
"array",
"null"
],
"items": {
"type": "object",
"properties": {
"objectId": {
"type": "string"
},
"name": {
"type": [
"string",
"null"
]
},
"workspaceId": {
"type": [
"string",
"null"
]
},
"configuredBy": {
"type": [
"string",
"null"
]
}
}
}
}
}
}
}
}
}Microsoft’s response uses:
Workspace ID: id
Dataflow ID: objectId
Workspace array: value
Dataflows array: dataflows
The use of objectId rather than id for dataflows is confirmed in Microsoft’s AdminDataflow schema.
7. Add the workspace loop
Still inside DoUntilNoMorePages, directly below ParseWorkspaceResponse, add:
Control → Apply to eachRename it:
ForEachWorkspaceFor Select an output from previous steps, use this expression:
coalesce(
body('ParseWorkspaceResponse')?['value'],
json('[]')
)Open the Apply to each settings and ensure:
Concurrency control: OffConcurrency must remain off because the flow increments shared variables and appends to a shared array.
8. Add the dataflow loop
Inside ForEachWorkspace, add another:
Control → Apply to eachRename it:
ForEachDataflowUse this input expression:
coalesce(
items('ForEachWorkspace')?['dataflows'],
json('[]')
)This is important because many workspaces will not contain any dataflows. Without coalesce, a missing or null dataflows property can stop the flow.
Again ensure:
Concurrency control: Off9. Add the Try scope
Inside ForEachDataflow, add:
Control → ScopeRename it:
TryRefreshEverything in the next steps belongs inside this scope.

9.1 Submit the dataflow refresh
Inside TryRefresh, add another Invoke an HTTP request action.
Rename it:
SubmitRefreshConfigure:
Method: POST
URL expression:
concat(
'/v1.0/myorg/groups/',
items('ForEachWorkspace')?['id'],
'/dataflows/',
items('ForEachDataflow')?['objectId'],
'/refreshes'
)Headers:
Header | Value |
Content-Type | application/json |
Accept | application/json |
Body:
{
"notifyOption": "MailOnFailure"
}Do not use MailOnCompletion; it is not supported for this endpoint. Microsoft documents MailOnFailure and NoNotification as the supported choices. A response code of 200 means the request was accepted, not that the refresh finished successfully. Microsoft: Refresh Dataflow
9.2 Increment the submitted count
Below SubmitRefresh, still inside TryRefresh, add:
Variables → Increment variableConfigure:
Name: SubmittedCount
Value: 1
9.3 Build the success log object
Add:
Data Operations → ComposeRename it:
BuildSubmittedLogPaste this into Expression:
setProperty(
setProperty(
setProperty(
setProperty(
setProperty(
json('{}'),
'workspaceId',
items('ForEachWorkspace')?['id']
),
'workspaceName',
items('ForEachWorkspace')?['name']
),
'dataflowId',
items('ForEachDataflow')?['objectId']
),
'dataflowName',
items('ForEachDataflow')?['name']
),
'status',
'Submitted'
)This produces:
{
"workspaceId": "...",
"workspaceName": "...",
"dataflowId": "...",
"dataflowName": "...",
"status": "Submitted"
}The setProperty() expression is supported in Power Automate and returns a real JSON object rather than a JSON-looking string. Microsoft workflow expression reference
9.4 Append the success entry
Add:
Variables → Append to array variableConfigure:
Name: RefreshLog
Value expression:
outputs('BuildSubmittedLog')9.5 Add pacing
Add:
Schedule → DelayConfigure:
Count: 1
Unit: Second
This avoids rapidly submitting a large burst of requests to Power BI and Fabric capacity.
10. Add the Catch scope
Directly underneath TryRefresh, but still inside ForEachDataflow, add another Scope.
Rename it:
CatchFailureOpen the three-dot menu for CatchFailure and select Configure run after.
For TryRefresh, select only:
has failed
has timed out
Clear:
is successful
is skipped
Microsoft recommends scopes plus Configure run after for Try/Catch error-handling patterns. Microsoft Power Automate error handling

10.1 Increment the failure count
Inside CatchFailure, add:
Variables → Increment variableConfigure:
Name: FailedCount
Value: 1
10.2 Build the failure log
Add a Compose action and rename it:
BuildFailureLogExpression:
setProperty(
setProperty(
setProperty(
setProperty(
setProperty(
setProperty(
json('{}'),
'workspaceId',
items('ForEachWorkspace')?['id']
),
'workspaceName',
items('ForEachWorkspace')?['name']
),
'dataflowId',
items('ForEachDataflow')?['objectId']
),
'dataflowName',
items('ForEachDataflow')?['name']
),
'status',
'Failed'
),
'error',
string(result('TryRefresh'))
)The result('TryRefresh') expression captures the status and output of actions inside the failed scope. It is safer than directly reading the output of a timed-out HTTP action.

10.3 Append the failure entry
Add:
Variables → Append to array variableConfigure:
Name: RefreshLog
Value:
outputs('BuildFailureLog')Optionally add another one-second Delay inside CatchFailure.
11. Update pagination
The following two actions belong inside DoUntilNoMorePages, but outside ForEachWorkspace.
Be careful with this placement. Collapse ForEachWorkspace first, and then add the actions directly below it.
11.1 Set MorePages
Add:
Variables → Set variableRename it:
SetMorePagesConfigure:
Name: MorePages
Value expression:
equals(
length(
coalesce(
body('ParseWorkspaceResponse')?['value'],
json('[]')
)
),
variables('PageSize')
)Logic:
1,000 returned workspaces → MorePages = true
Fewer than 1,000 → MorePages = false
Exactly 1,000 on the final real page → one additional empty API call is made, then the loop stops safely
11.2 Increment Skip
Below SetMorePages, add:
Variables → Increment variableRename it:
IncrementSkipConfigure:
Name: Skip
Value expression:
variables('PageSize')After the first page, Skip becomes 1,000; then 2,000; and so on.
12. Add the final summary
Outside and underneath DoUntilNoMorePages, add:
Data Operations → ComposeRename it:
RefreshSummaryExpression:
concat(
'Refresh requests submitted: ',
string(variables('SubmittedCount')),
'; failed: ',
string(variables('FailedCount')),
'; total processed: ',
string(
add(
variables('SubmittedCount'),
variables('FailedCount')
)
)
)Example result:
Refresh requests submitted: 24; failed: 2; total processed: 2613. Optionally create an HTML result table
Below RefreshSummary, add:
Data Operations → Create HTML tableConfigure:
From:
variables('RefreshLog')Columns: Automatic
You can then add:
Office 365 Outlook → Send an email (V2)Use:
Subject:
Power BI Dataflow Refresh SummaryBody:
<p>@{outputs('RefreshSummary')}</p>
@{body('Create_HTML_table')}Enable HTML if your Outlook action provides that setting.

14. Final flow structure
Your finished flow should have exactly this nesting:
Manually trigger a flow
Initialize PageSize
Initialize Skip
Initialize MorePages
Initialize SubmittedCount
Initialize FailedCount
Initialize RefreshLog
DoUntilNoMorePages
GetActiveWorkspaces
ParseWorkspaceResponse
ForEachWorkspace
ForEachDataflow
TryRefresh
SubmitRefresh
Increment SubmittedCount
BuildSubmittedLog
Append success to RefreshLog
Delay
CatchFailure
Increment FailedCount
BuildFailureLog
Append failure to RefreshLog
SetMorePages
IncrementSkip
RefreshSummary
Create HTML table
Send email
15. (optional) Test safely before tenant-wide execution
Do not immediately refresh every discovered dataflow.
Temporarily add a Condition inside ForEachDataflow, before TryRefresh, using:
and(
equals(
items('ForEachWorkspace')?['name'],
'YOUR TEST WORKSPACE'
),
equals(
items('ForEachDataflow')?['name'],
'YOUR TEST DATAFLOW'
)
)Compare that expression with Boolean:
truePlace TryRefresh and CatchFailure in the Yes branch. This makes the first test submit only one selected dataflow.
After the test succeeds:
Remove the temporary condition and place the scopes directly inside ForEachDataflow, or
Change the condition so every returned dataflow is allowed.
What this flow does not do
This design does not:
Wait for dataflows to finish.
Process dependencies in order.
Refresh Dataflow Gen2 items.
Automatically grant workspace access.
Guarantee that a Fabric administrator can refresh every workspace’s dataflows.
The signed-in administrator can discover tenant workspaces using the admin API, but discovery permission does not automatically provide write access to workspace content. If the account is not a Contributor, Member, or Admin in a target workspace, the corresponding refresh can return 403 Forbidden.
For dependency-sensitive processing, the next version should submit a refresh, poll its transaction status until success or failure, and only then continue to the dependent dataflow or semantic model.
Practical Example
Assume the tenant contains three active workspaces. Finance contains Cash Flow and General Ledger; Operations contains Stock and Visits; Sandbox contains no dataflows. The admin request returns all three workspaces in one page. The outer loop processes each workspace, while the inner loop submits four refresh requests and skips Sandbox because its dataflows array is empty.
If the identity can refresh three dataflows but lacks access to Operations - Visits, the Try scope fails only for that item. Catch records the failure, FailedCount becomes 1, and the remaining requests continue. The final summary reads Refresh requests submitted: 3; failed: 1. Power BI then performs the accepted refreshes asynchronously.

Bonus Tips and Common Mistakes
Pro Tip: Separate discovery from execution
For sensitive tenants, add a PreviewOnly Yes/No input to the manual trigger. When PreviewOnly is Yes, populate RefreshLog but skip the POST action. Administrators can confirm the scope before sending refresh requests.
Common Mistake: Treating 200 as refresh completion
The refresh endpoint confirms submission, not completion. Do not start a dependent semantic model simply because the POST returned 200. For controlled chains, query refresh history or use a completion event, evaluate the result, and only then start the dependent layer.
Good to Know: Respect API and capacity limits
Microsoft limits Get Groups as Admin to 50 requests per hour or 15 per minute per tenant, with a 30-second timeout. The selected page size normally keeps discovery well below that limit. Power BI can also return 429 when an API is throttled; use the Retry-After header and a retry policy rather than immediately resubmitting the entire tenant.
Good to Know: Dataflow Gen2 is different
Fabric Dataflow Gen2 has its own public API surface and job model. Do not assume the Power BI Dataflows Gen1 refresh endpoint will discover or refresh Gen2 items. Build a separate branch based on the current Fabric Data Factory APIs if your tenant uses both generations.
Troubleshooting
401 Unauthorized: confirm the connection requests a token for https://analysis.windows.net/powerbi/api rather than Microsoft Graph.
403 Forbidden on the admin GET: confirm the signed-in user is a Fabric administrator and the delegated token has Tenant.Read.All or Tenant.ReadWrite.All.
403 on one refresh POST: the identity can discover the dataflow but cannot write to its workspace. Add the approved workspace role or exclude that workspace.
429 Too Many Requests: honor Retry-After, reduce submission speed, and avoid parallel retries.
Null dataflows error: use coalesce(..., json('[]')) for the inner loop input.
Duplicate refreshes: prevent simultaneous manual runs with trigger concurrency control, or store a run lock before discovery.
Final Thoughts
A single manual Power Automate button can replace repetitive workspace-by-workspace refresh work, but tenant-wide automation deserves careful permissions, pagination, pacing, and failure handling. The flow in this guide discovers Dataflows Gen1 only in active workspaces, submits each refresh with the correct workspace and dataflow IDs, and leaves a clear audit trail. Start with PreviewOnly behavior, test one workspace, then expand the automation when the permissions and capacity impact are understood.



Comments