Forum Discussion
Using Power BI API in C#
- 7 months ago
Hi bigmac025 ,
Thanks for the update and for sharing the decoded token details.
To clarify the point around roles and permissions, the documentation is referring to delegated Power BI permissions when it states that admin-consent required permissions must not be present for service principal authentication. In an app-only setup, the app registration should not have any delegated Power BI permissions configured.
This does not conflict with assigning a Power BI Admin or Fabric Admin tenant role to the service principal. Tenant roles are evaluated by the Power BI service at runtime and are separate from Azure AD delegated permissions. Admin REST APIs such as GetGroupsAsAdminAsync() are authorized based on tenant-level configuration, not on delegated scopes in the token.
So to address both points together, including AmosHersch 's comment, the presence of Tenant.Read.All or Tenant.ReadWrite.All in an app-only token does not cause the 401 and does not need to be removed. Those scopes are relevant only for delegated admin user tokens and are ignored when using service principal authentication.
Since the token itself is valid, a 401 from GetGroupsAsAdminAsync() still points to tenant-level authorization. Please recheck that service principal access is enabled in the Power BI Admin portal and that the service principal or its security group is allowed, that the service principal has a Power BI Admin or Fabric Admin tenant role, and that no delegated Power BI permissions are configured on the app registration.
Once these are in place, the updated code using GetGroupsAsAdminAsync() and GetReportsAsAdminAsync() should work as expected.
Hope this helps. Please reach out for further assistance.
Thank you.
Hello v-veshwara-msft
I'm still having issues getting this C# app to work...
Thank you for all the help you have given me. I have read all suggestions and documentation sent to me.
Here is my modified code from all the help I have recieved:
class PowerBI
{
public async System.Threading.Tasks.Task getPowerBIWorkspaceData()
{
//instantiate the api object
API api = new API();
string accessToken = await api.getPowerBIAccessToken();
Console.WriteLine(accessToken);
var credentials = new TokenCredentials(accessToken, "Bearer");
//var apiUrl = "https://removed"; // Base URL for the Power BI Service API
var apiUrl = "https://api.powerbi.com/";
using (var client = new PowerBIClient(new Uri(apiUrl), credentials))
{
try
{
// Example: Get a list of workspaces (groups)
var workspaces = await client.Groups.GetGroupsAsAdminAsync(top: 500);
Console.WriteLine($"Found {workspaces.Value?.Count ?? 0} workspaces.");
// You can now call other APIs, e.g., to get reports in a specific workspace
if (workspaces.Value.Count > 0)
{
var groupId = workspaces.Value[0].Id;
var reports = await client.Reports.GetReportsAsAdminAsync(groupId);
Console.WriteLine($"Workspace has {reports.Value.Count} reports.");
}
}
catch (Microsoft.Rest.HttpOperationException ex)
{
Console.WriteLine($"Status: {(int)ex.Response.StatusCode} {ex.Response.StatusCode}");
Console.WriteLine("Response content:");
Console.WriteLine(ex.Response.Content);
//If headers exist, print them (sometimes the key clue is here)
if (ex.Response.Headers != null)
{
foreach (var h in ex.Response.Headers)
Console.WriteLine($"{h.Key}: {string.Join(",", h.Value)}");
}
}
}
}
}
class API
{
public async Task<string> getPowerBIAccessToken()
{
var tenantId = "removed";
var clientId = "removed";
var clientSecret = "removed";
// The Power BI API scope
var scopes = new[] { "https://analysis.windows.net/powerbi/api/.default" };
var app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri($"https://login.microsoftonline.com/{tenantId}"))
.Build();
var authResult = await app.AcquireTokenForClient(scopes).ExecuteAsync();
return authResult.AccessToken;
}
}
I still get this error:
Status: 401 Unauthorized
Response content:
Pragma: no-cache
Transfer-Encoding: chunked
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: deny
X-Content-Type-Options: nosniff
RequestId: ab38caa1-e7f7-47de-97d1-3a3101cce68e
Access-Control-Expose-Headers: RequestId
request-redirected: true
home-cluster-uri: https://wabi-germany-west-central-primary-redirect.analysis.windows.net/
Cache-Control: no-store, must-revalidate, no-cache
Date: Mon, 12 Jan 2026 21:03:49 GMT
Content-Type: application/octet-stream
I then checked the token's aud claim and got this in the decoded token:
"roles": [
"Tenant.ReadWrite.All",
"Tenant.Read.All"
From my understanding this proves my MSAL token is correct for Power BI and it’s an app-only token.
Any other things I can check?
bigmac025 I suspect that for service principal you shouldn't have any admin roles in the token, see here: Admin - Groups GetGroupsAsAdmin - REST API (Power BI Power BI REST APIs) | Microsoft Learn
"Tenant.Read.All or Tenant.ReadWrite.All
Relevant only when authenticating via a standard delegated admin access token. Must not be present when authentication via a service principal is used."
Can you try again after removing those roles?