[[About|Andrew Boerger]] | Feb 2, 2025
Over the last year or so, the paginated report experience in the Power BI Service has come a long way. As paginated reports become simpler to create, you may find the need to re-bind a report to a different semantic model. This could be an ownership handoff, involving moving a report from a personal workspace into a shared pipeline with multiple deployment stages.
In any case, last week we wanted to re-bind multiple paginated reports to a new semantic model. We could download each report as an `.rdl` file, manually make the changes, and republish - but surely there's a better way? After all, making offline changes to paginated reports in Report Builder often results in them becoming un-editable in the Service.
We know that ordinary Power BI reports can be re-bound using `Reports - Rebind Report In Group`, but this method explicitly does not support paginated reports. So, is there an alternative approach?
The good news is that yes, there is a relatively simple solution. However, it may not be obvious when reviewing the Power BI REST API docs for the first time.
Rebinding a paginated report requires two API calls:
1. [Reports - Take Over In Group ](https://learn.microsoft.com/en-us/rest/api/power-bi/datasets/update-datasources-in-group)
2. [Reports - Update Datasources In Group](https://learn.microsoft.com/en-us/rest/api/power-bi/reports/update-datasources-in-group)
You can call these APIs using your preferred tool. The examples below are compatible with a Python or PySpark notebook in Fabric.
## Authentication
Both endpoints support service principal authentication. Alternatively, you can use the `notebookutils` package for pass-through authentication.
If you choose to use a service principal, keep in mind the following:
- The service principal must be authorized for overall PBI/Fabric API usage in the tenant settings
- The SP must have the correct API permissions assigned in Entra ID (in this case, specifically `Reports.ReadWrite.All`)
- The SP itself or it's security group must have permissions to the relevant report and dataset (workspace Contributor or above will work).
With that being said, let's authenticate using either option:
```python
# Config & imports
import json, requests
import azure.identity
from azure.identity import ClientSecretCredential
```
```python
# Authentication Option 1 (Service Principal)
tenant = "<YOUR TENANT ID HERE>"
client = "<YOUR CLIENT/APP ID HERE>"
client_secret = "<YOUR CLIENT SECRET HERE>"
# Generates the access token for the Service Principal
api = 'https://analysis.windows.net/powerbi/api/.default'
auth = ClientSecretCredential(authority = 'https://login.microsoftonline.com/',
tenant_id = tenant,
client_id = client,
client_secret = client_secret)
access_token = auth.get_token(api)
access_token = access_token.token
print('\nSuccessfully authenticated.')
```
```python
# Authentication Option 2 (Notebook user)
access_token = notebookutils.credentials.getToken("pbi")
```
## Taking ownership
Next, we need to take ownership of the paginated report, either personally or through a service principal. Specify the workspace and report GUID, and `post` to the `Take Over In Group` endpoint.
```python
# Take over paginated report
pbi_url = "https://api.powerbi.com/v1.0/myorg/groups/<YOUR WORKSPACE GUID HERE>/reports/<YOUR REPORT GUID HERE>/Default.TakeOver"
response = requests.post(pbi_url, headers=header)
if response.status_code == 200:
print("Report taken over.")
else:
print(f"Failed to take over report. Status code: {response.status_code}")
print(f"Response: {response.text}")
```
## Binding
Now that we've taken over the report, we can re-bind it. This is where things get tricky, as the official documentation for `Update Datasources In Group` only gives us examples of re-binding a paginated report to a new SQL data source, not a new semantic model:
```json
{
"updateDetails": [
{
"datasourceName": "SqlDatasource",
"connectionDetails": {
"server": "New-Sql-Server",
"database": "New-Sql-Database"
}
},
{
"datasourceName": "SqlAzureDatasource",
"connectionDetails": {
"server": "New-SqlAzure-Server.windows.net",
"database": "New-SqlAzure-Database"
}
}
]
}
```
So what does our request body need to look like? Fortunately, [a solved Fabric Community post](https://community.fabric.microsoft.com/t5/Desktop/Can-t-Update-paginated-report-data-source-of-as-semantic-model/td-p/4334341) from December 2024 gets us the rest of the way. Special thanks to **Mestu_Paul** for the working payload example:
```JSON
{
"updateDetails": [
{
"datasourceName": "<dataSourceName>",
"connectionDetails": {
"server": "pbiazure://api.powerbi.com/;",
"database": "sobe_wowvirtualserver-<datasetId>"
}
}
]
}
```
We found that specifying the server isn't necessary, as only the database needs to be changed.
Now, let's put the pieces together and re-bind our report:
```python
# Rebind paginated report
payload = {
"updateDetails": [
{
"datasourceName": "<YOUR PAGINATED REPORT DATASOURCE NAME HERE>",
"connectionDetails": {
"database": "sobe_wowvirtualserver-<YOUR SEMANTIC MODEL GUID HERE>"
}
}
]
}
pbi_url = "https://api.powerbi.com/v1.0/myorg/groups/<YOUR WORKSPACE GUID HERE>/reports/<YOUR REPORT GUID HERE>/Default.UpdateDatasources"
response = requests.post(pbi_url, headers=header, json=payload)
if response.status_code == 200:
print("Datasources successfully updated.")
else:
print(f"Failed to update datasources. Status code: {response.status_code}")
print(f"Response: {response.text}")
```
Now, you can efficiently rebind paginated reports to different semantic models. You could easily extend the above method to operate in bulk, or otherwise modify it to suit your needs.