Forum Discussion
Service Principal Approach to Extract Data from Power BI Semantic Models via Python
Power BI's REST API allows secure, programmatic access to datasets—also known as semantic models—enabling automation, integration, and advanced analytics. In enterprise environments, Service Principal authentication is the preferred method because it doesn't require a user to be present, supports automation, and follows best practices for security and governance.
In this guide, you'll learn how to use Python, MSAL, and a service principal to authenticate and query a Power BI semantic model.
🚀Why Use a Service Principal?
✅No dependency on a user login
✅Perfect for automation or backend jobs
✅Fine-grained control over access using Azure AD roles and Power BI workspace membership
✅Better auditability and governance
🔧Prerequisites
You’ll need:
🔐An Azure AD registered application (Service Principal)
🧾 Tenant ID, Client ID, and Client Secret
📊Power BI workspace with a semantic model (dataset)
🛡️ Admin access to configure permissions
🧾Install MSAL
pip install msal requests
🛠️ Step-by-Step Setup
1. Register the App in Entra ID
Go to Entra Admin Center
Navigate to App registrations > New registration
Record the Application (client) ID and Directory (tenant) ID
2. Create a Client Secret
Under Certificates & Secrets, add a New client secret
Save it securely—you won't be able to view it again later
3. Grant API Permissions
Go to API Permissions > Add a permission:
Power BI Service
Dataset.Read.All OR Dataset.ReadWrite.All
🔔Don’t forget to grant admin consent for these permissions.
4. Enable Service Principal Access in Power BI Admin Portal
Go to Power BI > Admin Portal > Tenant settings
Scroll to Developer settings
Enable "Service principals can use Power BI APIs"
5. Add Service Principal to Workspace
Go to the Power BI workspace
Click Manage access
Add your service principal (the app’s name) as a Viewer or Member
Once everything is set use the following Python code to extract the data
🧠 Python Code Example
import requests
from msal import ConfidentialClientApplication
import json
# === Configuration ===
TENANT_ID = 'your-tenant-id'
CLIENT_ID = 'your-client-id'
CLIENT_SECRET = 'your-client-secret'
WORKSPACE_ID = 'your-workspace-id'
DATASET_ID = 'your-dataset-id'
# === Authentication ===
AUTHORITY = f'https://login.microsoftonline.com/{TENANT_ID}'
SCOPE = ['https://analysis.windows.net/powerbi/api/.default']
app = ConfidentialClientApplication(
CLIENT_ID,
authority=AUTHORITY,
client_credential=CLIENT_SECRET
)
result = app.acquire_token_for_client(scopes=SCOPE)
if "access_token" not in result:
raise Exception(f"Token acquisition failed: {result.get('error_description')}")
# === Run DAX Query ===
url = f"https://api.powerbi.com/v1.0/myorg/groups/{WORKSPACE_ID}/datasets/{DATASET_ID}/executeQueries"
headers = {
'Authorization': f"Bearer {result['access_token']}",
'Content-Type': 'application/json'
}
payload = {
"queries": [
{
"query": "EVALUATE VALUES(YourTable)"
}
],
"serializerSettings": {
"includeNulls": True
}
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 200:
print(json.dumps(response.json(), indent=2))
else:
print(f"Error {response.status_code}: {response.text}")
Expected output
📌Final Thoughts
The service principal-based approach is the most secure and scalable way to interact with Power BI datasets. It enables backend systems, automation workflows, and enterprise-grade solutions to query data without manual intervention.
Please let me know if this post helped solve your problem. If so, I’ll mark it as solved to make it easier for others to find for similar future queries. Thanks 😊
6 Replies
- kushanNaSuper User
Please let me know if this post helped solve your problem. If so, I’ll mark it as solved to make it easier for others to find for similar future queries. Thanks 😊
- philip_grayRegular Visitor
I just tried running this code with a service principal and it returns the following:
Error 401: {"error":{"code":"PowerBINotAuthorizedException","pbi.error":{"code":"PowerBINotAuthorizedException","parameters":{},"details":[],"exceptionCulprit":1}}}I think my service principal is configured correctly because when I change the URL to...
...I get a valid JSON response.
I suspect that this is because the Dataset.Read.All and Dataset.ReadWrite.All API permissions are delegated permissions not application permissions. Which would mean that they only take effect when a user is accessing the enterprise application. Is this correct?
Decoding the JWT generated seems to support this - only the application permissions are in the token:
"roles": [ "Tenant.ReadWrite.All", "Tenant.Read.All" ]Am I missing something in my configuration? Has something changed in the REST API?
I'd greatly appreciate any assistance you can provide with this.
- kushanNaSuper User
Hi philip_gray
Yes, I’m using delegated permissions. actually I’m not an expert with the technical side of Azure app permissions, but my understanding is that delegated permissions mean the app acts on behalf of a user.
and I have tested it right now and it's still working for me :
- philip_grayRegular Visitor
kushanNa Thanks for the response - much appreciated.
Would you be able to decode your JWT (the accesss_token part of the result variable) and confirm:
- the "idtyp" value
- The content of the "Roles" array
If you're using a service principal then the idtyp should be app and it shouldn't be possible for the roles to contain Dataset.Read.All or Dataset.ReadWrite.All - as these are only available as delegated permissions.