top of page
9d657493-a904-48e4-b46b-e08acb544ddf.png

POSTS

Refresh All Power BI Dataflows with One Power Automate Button

  • Writer: MirVel
    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.

Flowchart on white background: Manual trigger to initialize variables, process workspaces/dataflows, refresh, log results, update pagination, create summary.
Power Automate flow map

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.


Complete flow, where things get complicated
Complete flow, where things get complicated

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=1
  • Headers: leave empty

  • Body: leave empty

Save and test the flow.

Expected result:

Status code: 200

The 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 flow

Open its Settings:

  • Concurrency control: On

  • Degree of parallelism: 1

This prevents two users from starting the complete tenant refresh simultaneously.

3. Initialize the variables


Power Automate flow editor showing variable steps and a RefreshLog parameter pane; nodes include vPageSize, vSkip, vMorePages.

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:

  1. Manually trigger a flow

  2. Initialize PageSize

  3. Initialize Skip

  4. Initialize MorePages

  5. Initialize SubmittedCount

  6. Initialize FailedCount

  7. Initialize RefreshLog

4. Add the pagination loop

After the variables, add:

Control → Do until

Rename it:

DoUntilNoMorePages

Configure 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 request

Rename it:

GetActiveWorkspaces

Configure:

  • 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


Power Automate flow editor showing DoUntilNoMorePages loop, with steps vSkip, vMorePages, vSubmittedCount, vFailedCount and settings 100, PT12H

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 JSON

Rename it:

ParseWorkspaceResponse

For 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 each

Rename it:

ForEachWorkspace

For Select an output from previous steps, use this expression:

coalesce(
  body('ParseWorkspaceResponse')?['value'],
  json('[]')
)

Open the Apply to each settings and ensure:

Concurrency control: Off

Concurrency 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 each

Rename it:

ForEachDataflow

Use 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: Off

9. Add the Try scope

Inside ForEachDataflow, add:

Control → Scope

Rename it:

TryRefresh

Everything in the next steps belongs inside this scope.

Workflow diagram on a white canvas showing steps GetActiveWorkspaces, ParseWorkspaceResponse, ForEachWorkspace, ForEachDataflow, TryRefresh, and DoUntilNoMorePages

9.1 Submit the dataflow refresh

Inside TryRefresh, add another Invoke an HTTP request action.

Rename it:

SubmitRefresh

Configure:

  • 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 variable

Configure:

  • Name: SubmittedCount

  • Value: 1


9.3 Build the success log object

Add:

Data Operations → Compose

Rename it:

BuildSubmittedLog

Paste 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 variable

Configure:

  • Name: RefreshLog

  • Value expression:

outputs('BuildSubmittedLog')

9.5 Add pacing

Add:

Schedule → Delay

Configure:

  • 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:

CatchFailure

Open 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


Screenshot of a workflow canvas with nested ForEachWorkspace and ForEachDataflow steps, TryRefresh/CatchFailure blocks and logs.

10.1 Increment the failure count

Inside CatchFailure, add:

Variables → Increment variable

Configure:

  • Name: FailedCount

  • Value: 1


10.2 Build the failure log

Add a Compose action and rename it:

BuildFailureLog

Expression:

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.


Workflow diagram showing highlighted ForEachWorkspace loop between GetActiveWorkspaces, ParseWorkspaceResponse, SetMorePages, and IncrementSkip.

10.3 Append the failure entry

Add:

Variables → Append to array variable

Configure:

  • 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 variable

Rename it:

SetMorePages

Configure:

  • 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 variable

Rename it:

IncrementSkip

Configure:

  • 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 → Compose

Rename it:

RefreshSummary

Expression:

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: 26

13. Optionally create an HTML result table

Below RefreshSummary, add:

Data Operations → Create HTML table

Configure:

  • From:

variables('RefreshLog')
  • Columns: Automatic

You can then add:

Office 365 Outlook → Send an email (V2)

Use:

  • Subject:

Power BI Dataflow Refresh Summary
  • Body:

<p>@{outputs('RefreshSummary')}</p>
@{body('Create_HTML_table')}

Enable HTML if your Outlook action provides that setting.


Flowchart of a Power Automate process with steps ForEachWorkspace, SetMorePages, IncrementSkip, RefreshSummary, OutputTable, Email.

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
Workflow diagram with green checkmarks for steps like Manually trigger a flow, vPageSize, RefreshSummary, and Email ending successfully
Flow completed without errors

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:

true

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


Power BI Dataflow Refresh report showing 2 submitted, 0 failed, both dataflows marked Submitted in a table.
Adjusted flow to send formatted HTML email (statuses)

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

Rated 0 out of 5 stars.
No ratings yet

Add a rating
Page Logo

Turn Messy Data into Clear Dashboards and Better Decisions.

Explore

Contact

Address:
83022 Rosenheim, Germany

Join Our Newsletter

Get a free Power Query cheat sheet by subscribing!

© Excelized. All rights reserved.

bottom of page