C# and Power BI REST API

Michal Molka
3 min readSep 27, 2024

--

In the last article. We configured access to Power BI REST API: Power BI REST API — configuration

Today we will make some calls from a C# app.

First things first. Power BI REST API uses a service principal only for READ request, so we can’t make any changes using these calls. If we want to get a write access, we can use a standard user account with proper privileges. The code below supports these two situations. You can pick whether you want to use service principal or a user account.

bool readMode = false;
string token;

if (readMode) // Type of a credential we want to use
{
var credential = new ClientSecretCredential(tenantID, clientID, secret); // A service principal
token = credential.GetToken(new Azure.Core.TokenRequestContext(new[] { "https://analysis.windows.net/powerbi/api/.default" })).Token.ToString();
}
else
{ // Authenticate first: az login --scope https://graph.microsoft.com/.default or use an Azure credential
var credential = new ChainedTokenCredential(new AzureCliCredential(), new DefaultAzureCredential()); // A user account
token = credential.GetToken(new Azure.Core.TokenRequestContext(new[] { "https://analysis.windows.net/powerbi/api/.default" })).Token.ToString();
}

HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

A Bearer token is extracted here and used for requests. Then it is added as a header into an Http client.

As an interesting side note. Here is an example how to extract the Bearer token in Postman.

https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token

Body parameters:
client_id: {clientId}
client_secret: {clientSecret}
grant_type: client_credentials
scope: https://analysis.windows.net/powerbi/api/.default

Everything is set up. So, we can make a few calls.

A list of workspaces.

var workspacesResponse = client.GetStringAsync("https://api.powerbi.com/v1.0/myorg/groups");
Console.WriteLine(workspacesResponse.Result);


A result:
{
"@odata.context":"https://wabi-west-europe-e-primary-redirect.analysis.windows.net/v1.0/myorg/$metadata#groups","@odata.count":1,"value":[
{
"id":"7*****a","isReadOnly":false,"isOnDedicatedCapacity":true,"capacityId":"B*****5","defaultDatasetStorageFormat":"Large","type":"Workspace","name":"Iowa_Sales"
}
]
}

Dataflow data sources.

var dataflowSourcesResponse = client.GetStringAsync($"https://api.powerbi.com/v1.0/myorg/groups/{workspaceId}/dataflows/{dataflowId}/datasources");
Console.WriteLine(dataflowSourcesResponse.Result);

A response:
{
"@odata.context":"https://wabi-west-europe-e-primary-redirect.analysis.windows.net/v1.0/myorg/groups/7***aa/$metadata#datasources","value":[
{
"datasourceType":"Web","connectionDetails":{
"url":"https://*****.sharepoint.com/personal/m*****com"
},"datasourceId":"3*****c","gatewayId":"2******f"
}
]
}

Reports from a workspace.

var reportsResponse = client.GetStringAsync($"https://api.powerbi.com/v1.0/myorg/groups/{workspaceId}/reports");
Console.WriteLine(reportsResponse.Result);

A response:
{
"@odata.context":"https://wabi-west-europe-e-primary-redirect.analysis.windows.net/v1.0/myorg/groups/7*****a/$metadata#reports","value":[
{
"id":"4*****6","reportType":"PowerBIReport","name":"Iowa_Liquor_Sales_Power_Automate","webUrl":"https://app.powerbi.com/groups/7*****a/reports/4*****6","embedUrl":"https://app.powerbi.com/reportEmbed?reportId=4*****6&groupId=7*****a&w=2&config=ey*****d","isFromPbix":true,"isOwnedByMe":true,"datasetId":"4*****5","datasetWorkspaceId":"7*****a","users":[

],"subscriptions":[

],"sections":[

]
},{
"id":"8*****8","reportType":"PowerBIReport","name":"Report Usage Metrics Report","webUrl":"https://app.powerbi.com/groups/7*****a/reports/8*****8","embedUrl":"https://app.powerbi.com/reportEmbed?reportId=8*****e8&groupId=7*****d","isFromPbix":false,"isOwnedByMe":true,"datasetId":"a*****c","datasetWorkspaceId":"7*****a","users":[

],"subscriptions":[

],"sections":[

]
},{
"id":"4*****c","reportType":"PowerBIReport","name":"Usage Metrics Report","webUrl":"https://app.powerbi.com/groups/7*****a/reports/4*****c","embedUrl":"https://app.powerbi.com/reportEmbed?reportId=4*****c&groupId=7*****a&w=2&config=ey*****d","isFromPbix":true,"isOwnedByMe":true,"datasetId":"9*****a","datasetWorkspaceId":"7*****a","users":[

],"subscriptions":[

],"sections":[

]
},{
"id":"9*****8","reportType":"PowerBIReport","name":"Iowa_Liquor_Sales","webUrl":"https://app.powerbi.com/groups/7*****a/reports/9*****8","embedUrl":"https://app.powerbi.com/reportEmbed?reportId=9*****8&groupId=7*****a&w=2&config=ey*****d","isFromPbix":true,"isOwnedByMe":true,"datasetId":"6*****6","datasetWorkspaceId":"7*****a","users":[

],"subscriptions":[

],"sections":[

]
}
]
}

Refresh a dataflow.

var values01 = JsonSerializer.Serialize(new { notifyOption = "NoNotification" });
var requestContent01 = new StringContent(values01, Encoding.UTF8, "application/json");
var refreshDataset = client.PostAsync($"https://api.powerbi.com/v1.0/myorg/groups/{workspaceId}/dataflows/{dataflowId}/refreshes", requestContent01);
Console.WriteLine(refreshDataset.Result);

A response:
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
Cache-Control: no-store, must-revalidate, no-cache
Pragma: no-cache
Transfer-Encoding: chunked
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: deny
X-Content-Type-Options: nosniff
RequestId: 60585822-0f6a-4a71-9f1a-954a56ad6cbf
Access-Control-Expose-Headers: RequestId
request-redirected: true
home-cluster-uri: https://wabi-west-europe-e-primary-redirect.analysis.windows.net/
Date: Thu, 20 Jun 2024 09:30:05 GMT
Content-Type: application/octet-stream
}

Add an admin user to a workspace.

var values02 = JsonSerializer.Serialize(new { emailAddress = "L*****H@e*****t.com", groupUserAccessRight = "Admin" });
var requestContent02 = new StringContent(values02, Encoding.UTF8, "application/json");
var workspaceAddUser = client.PostAsync($"https://api.powerbi.com/v1.0/myorg/groups/{workspaceId}/users", requestContent02);
Console.WriteLine(workspaceAddUser.Result);

A response:
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
Cache-Control: no-store, must-revalidate, no-cache
Pragma: no-cache
Transfer-Encoding: chunked
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: deny
X-Content-Type-Options: nosniff
RequestId: f636a30f-d565-4adb-b0e8-ecfed6337041
Access-Control-Expose-Headers: RequestId
request-redirected: true
home-cluster-uri: https://wabi-west-europe-e-primary-redirect.analysis.windows.net/
Date: Thu, 20 Jun 2024 09:31:52 GMT
Content-Type: application/octet-stream
}

Change a dataset storage mode.

var values03 = JsonSerializer.Serialize(new { targetStorageMode = "PremiumFiles" }); // PremiumFiles <- a large storage mode, Abf <- small datasets
var requestContent03 = new StringContent(values03, Encoding.UTF8, "application/json");
var storageModeDataset = client.PatchAsync($"https://api.powerbi.com/v1.0/myorg/datasets/{datasetId}", requestContent03);
Console.WriteLine(storageModeDataset.Result);

A response:
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
Cache-Control: no-store, must-revalidate, no-cache
Pragma: no-cache
Transfer-Encoding: chunked
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: deny
X-Content-Type-Options: nosniff
RequestId: c2f15a9e-ac50-4bd5-92e9-21f33701d84e
Access-Control-Expose-Headers: RequestId
request-redirected: true
home-cluster-uri: https://wabi-west-europe-e-primary-redirect.analysis.windows.net/
Date: Thu, 20 Jun 2024 09:32:57 GMT
Content-Type: application/octet-stream
}

And here is a full C# code on GitHub: powerbi_rest_api.cs

--

--

Michal Molka

Architect | Azure | Power BI | Fabric | Power Platform | Infrastructure | Security | M365