# Semantic model Data Refresh API in Power BI

One of the newly added feature in Power BI Feb 2025 update was “**Link semantic model refresh details from external applications**”. More details can be found [here](https://learn.microsoft.com/en-us/power-bi/connect-data/refresh-data#link-from-external-applications) and also the official announcement is seen [here](https://powerbi.microsoft.com/en-us/blog/power-bi-february-2025-feature-summary/?cdn=disable#post-29024-_Toc162811831).

This article will examine the recent updates to the refresh APIs and how granular details of the semantic model refreshes can be exposed as an URL.

The article will focus primarily on how to leverage Refresh API’s to

* Refresh a specific semantic model in a given workspace
    
* Refresh all the semantic models of a given workspace
    
* Check the statuses of the refreshes and construct a refresh URL
    

### SetUp

To get started, our service principal should have `Dataset.Read.All` and `Dataset.ReadWrite.All` delegated permissions for the Power BI service.

![Power BI REST API](https://cdn.hashnode.com/res/hashnode/image/upload/v1739999461454/474367ba-0b07-469d-a8bc-ea393178d2b8.png?auto=compress,format&format=webp align="left")

We will have to use the [`https://analysis.windows.net/powerbi`](https://analysis.windows.net/powerbi) scope.

Create a new C# console application and add the following settings to the `local.settings.json` file

```json
{
  "AllowedHosts": "*",
  "ClientId": "ClientId of the service prinicipal",
  "Workspace": "Workspace where the semantic model resides",
  "Semantic Model": "Name of the semantic model"
}
```

Add the following Nuget packages to the console application

```json
dotnet add package Azure.Identity --version 1.8.0
dotnet add package Microsoft.Extensions.Configuration --version 6.0.0
dotnet add package Microsoft.Identity.Client --version 4.47.0
dotnet add package Newtonsoft.Json --version 13.0.4
```

### **Code**

**Add required references to the code**

```csharp
using Microsoft.Extensions.Configuration;
using Microsoft.Identity.Client;
using Newtonsoft.Json.Linq;
using System.Net.Http.Headers;
using System.Text;
```

**Declare a bunch of variables**

```csharp
 public static HttpClient client = new();
 public static string response = "";
 public static string url = "";
 public static string semanticmodel = "";
 public static string clientId = "";
 public static string workspace = "";
 private static string[] scopes = new string[] { "https://analysis.windows.net/powerbi/api/.default" };
 private static string Authority = "https://login.microsoftonline.com/organizations";
 private static string RedirectURI = "http://localhost";
```

**Read the** `localsettings.json` **file**

```csharp
  public static void ReadConfig()
  {
      var builder = new ConfigurationBuilder()
      .AddJsonFile($"local.settings.json", true, true);
      var config = builder.Build();
      clientId = config["ClientId"];
      workspace = config["Workspace"];
      semanticmodel = config["Semantic Model"];
  }
```

**Authenticate the user and generate the bearer token**

```csharp
   public async static Task<AuthenticationResult> ReturnAuthenticationResult()
   {
       string AccessToken;
       PublicClientApplicationBuilder PublicClientAppBuilder =
           PublicClientApplicationBuilder.Create(clientId)
           .WithAuthority(Authority)
           .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
           .WithRedirectUri(RedirectURI);

       IPublicClientApplication PublicClientApplication = PublicClientAppBuilder.Build();
       var accounts = await PublicClientApplication.GetAccountsAsync();
       AuthenticationResult result;
       try
       {
           result = await PublicClientApplication.AcquireTokenSilent(scopes, accounts.First())
                            .ExecuteAsync()
                            .ConfigureAwait(false);
       }
       catch
       {
           result = await PublicClientApplication.AcquireTokenInteractive(scopes)
                            .ExecuteAsync()
                            .ConfigureAwait(false);

       }

       return result;
   }
```

**GetAsync method**

```csharp
 public async static Task<string> GetAsync(string url)
 {
     AuthenticationResult result = await ReturnAuthenticationResult();
     client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

     HttpResponseMessage response = await client.GetAsync(url);
     try
     {
         response.EnsureSuccessStatusCode();
         return await response.Content.ReadAsStringAsync();
     }
     catch
     {
         Console.WriteLine(response.Content.ReadAsStringAsync().Result);
         return null;
     }

 }
```

**POST method to refresh all semantic models in a given workspace**

```csharp
    public static async Task<string> PostAsyncRefreshSemanticModel(string url)
    {
        AuthenticationResult result = await ReturnAuthenticationResult();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

        JObject jsonObject = new JObject
        {
            ["notifyOption"] = "MailOnFailure",
            ["retryCount"] = "3"
        };
        HttpRequestMessage httprequestmessage = new HttpRequestMessage
        {
            Method = HttpMethod.Post,
            RequestUri = new Uri(url),
            Content = new StringContent(jsonObject.ToString(), Encoding.UTF8, "application/json")

        };
        HttpResponseMessage response = await client.SendAsync(httprequestmessage);

        response.EnsureSuccessStatusCode();
        await response.Content.ReadAsStringAsync();
        return response.StatusCode.ToString();
    }
```

**POST method to refresh a specific semantic partition of a given workspace. This method is to be used only to refresh a specific partition**

```csharp
  public static async Task<string> PostAsyncRefreshPartition(string url)
  {
      AuthenticationResult result = await ReturnAuthenticationResult();
      client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

      JObject jsonObject = new JObject
      {
          ["type"] = "Full",
          ["commitMode"] = "transactional",
          ["maxParallelism"] = 2,
          ["retryCount"] = 2,
          ["timeout"] = "02:00:00",
          ["objects"] = new JArray
      {
          new JObject
          {
              ["table"] = "Dim_Customers",
              ["partition"] = "All Customers"
          }
      }
      };
      HttpRequestMessage httprequestmessage = new HttpRequestMessage
      {
          Method = HttpMethod.Post,
          RequestUri = new Uri(url),
          Content = new StringContent(jsonObject.ToString(), Encoding.UTF8, "application/json")

      };
      HttpResponseMessage response = await client.SendAsync(httprequestmessage);

      response.EnsureSuccessStatusCode();
      await response.Content.ReadAsStringAsync();
      return response.StatusCode.ToString();
  }
```

The method above is refreshing a partition called `All Customers` and table `Dim_Customers` in the partition. This method can be handy when a given partition does not require regular refreshes.

Details on the payload required for the API can be found [here](https://learn.microsoft.com/en-us/rest/api/power-bi/datasets/refresh-dataset-in-group#example-of-refreshing-one-partition) .

**Method to refresh all the semantic models in a given workspace.**

```csharp
 public async static Task<string> RefreshWorkspace()
 {
     response = await GetAsync("https://api.powerbi.com/v1.0/myorg/groups");
     JObject jobject_groups = JObject.Parse(response);
     JArray jArray_groups = (JArray)jobject_groups["value"];
     foreach (JObject path in jArray_groups)
     {
         if (path["name"].ToString() == workspace)
         {
             url = "https://api.powerbi.com/v1.0/myorg/groups/" + path["id"].ToString();
             url = url + "/datasets/";
             response = await GetAsync(url);
             JObject jobjectdatasets = JObject.Parse(response);
             JArray jArraydatasets = (JArray)jobjectdatasets["value"];
             foreach (JObject path_datasets in jArraydatasets)
             {
                 response = await PostAsyncRefreshSemanticModel(url + path_datasets["id"].ToString() + "/refreshes");
             }
         }

         url = "";
     }

     return "Success";
 }
```

**Note** : The approach above is to iterate across all the datasets for a given workspace and then make a call to the `PostAsyncRefreshSemanticModel` method.

**Method to refresh a specific semantic model in a given workspace.**

```csharp
     public async static Task<string> RefreshSemanticModel(string SemanticModel)
     {
         response = await GetAsync("https://api.powerbi.com/v1.0/myorg/groups");
         JObject jobject_groups = JObject.Parse(response);
         JArray jArray_groups = (JArray)jobject_groups["value"];
         foreach (JObject path in jArray_groups)
         {
             if (path["name"].ToString() == workspace)
             {
                 url = "https://api.powerbi.com/v1.0/myorg/groups/" + path["id"].ToString();
                 url = url + "/datasets/";
                 response = await GetAsync(url);
                 JObject jobjectdatasets = JObject.Parse(response);
                 JArray jArraydatasets = (JArray)jobjectdatasets["value"];
                 foreach (JObject path_datasets in jArraydatasets)
                 {
                     if (path_datasets["name"].ToString() == SemanticModel)
                     {
                         response = await PostAsyncRefreshSemanticModel(url + path_datasets["id"].ToString() + "/refreshes");
                     }

                 }

             }

             url = "";
         }

         return "Success";
     }
```

**Method to check the status of a refreshes**

```csharp
  public async static Task<Dictionary<string, object>> CheckStatus()
  {
      response = await GetAsync("https://api.powerbi.com/v1.0/myorg/groups");
      JObject jobject_groups = JObject.Parse(response);
      JArray jArray_groups = (JArray)jobject_groups["value"];
      Dictionary<string,object> refreshDictionary = new();
      foreach (JObject path in jArray_groups)
      {
          if (path["name"].ToString() == workspace)
          {
              url = "https://api.powerbi.com/v1.0/myorg/groups/" + path["id"].ToString();
              url = url + "/datasets/";
              response = await GetAsync(url);
              JObject jobjectdatasets = JObject.Parse(response);
              JArray jArraydatasets = (JArray)jobjectdatasets["value"];
              foreach (JObject path_datasets in jArraydatasets)
              {
                  response = await GetAsync(url + path_datasets["id"].ToString() + "/refreshes");
                  JObject JObjectrefreshresponse = JObject.Parse(response);
                  JArray JArrayrefreshresponse = (JArray)JObjectrefreshresponse["value"];
                  foreach(JObject pathrefresh in JArrayrefreshresponse)
                  {
                      refreshDictionary.Add(pathrefresh["requestId"].ToString(), pathrefresh);
                  }

              }

          }
      }

      return refreshDictionary;
  }
```

Below is the response of the refresh API

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1740440165502/87ede085-20ac-4241-b5b1-5b39d361263d.png align="center")

The return type of the `CheckStatus` method is a dictionary. Adding the details to the dictionary object of type `<string,object>`in the `CheckStatus`method.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1740440298943/1cdbb87c-63f3-4030-8bdb-75bc3848a734.png align="left")

### Refresh Details

The recent update indicates that, to retrieve additional detailed information about a specific refresh request, we can use the sample notebook code seen [here](https://learn.microsoft.com/en-us/power-bi/connect-data/refresh-data#link-from-external-applications).

On similar lines, to create a URL that points to the details of individual refresh id for an external application, we can modify the `CheckStatus` method to the following.

```csharp
  public async static Task<Dictionary<string, string>> CheckStatus()
  {
      response = await GetAsync("https://api.powerbi.com/v1.0/myorg/groups");
      JObject jobject_groups = JObject.Parse(response);
      JArray jArray_groups = (JArray)jobject_groups["value"];
      Dictionary<string,string> refreshDictionary = new();
      foreach (JObject path in jArray_groups)
      {
          if (path["name"].ToString() == workspace)
          {
              url = "https://api.powerbi.com/v1.0/myorg/groups/" + path["id"].ToString();
              url = url + "/datasets/";
              response = await GetAsync(url);
              JObject jobjectdatasets = JObject.Parse(response);
              JArray jArraydatasets = (JArray)jobjectdatasets["value"];
              foreach (JObject path_datasets in jArraydatasets)
              {
                  response = await GetAsync(url + path_datasets["id"].ToString() + "/refreshes");
                  JObject JObjectrefreshresponse = JObject.Parse(response);
                  JArray JArrayrefreshresponse = (JArray)JObjectrefreshresponse["value"];
                  foreach(JObject pathrefresh in JArrayrefreshresponse)
                  {
                      refreshDictionary.Add(pathrefresh["requestId"].ToString(), "https://app.powerbi.com/groups/" + path["id"].ToString() + "/datasets/" + path_datasets["id"].ToString() + "/refreshdetails/" + pathrefresh["requestId"].ToString());
                  }

              }

          }
      }

      return refreshDictionary;
  }
```

The output of the above method is still a Dictionary object but of the type `<string,string>` where the key stores the `RequestId` and value stores the constructed `URL` that points to the details of the refresh request.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1740442601562/d4977889-762b-42ce-b7c9-099110d53c46.png align="left")

Clicking on one of the URL values, displays the refresh details.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1740442774983/fd2ac173-e1a1-46a3-9034-61b293f34c49.png align="left")

### Complete Code

```csharp
using Microsoft.Extensions.Configuration;
using Microsoft.Identity.Client;
using Newtonsoft.Json.Linq;
using System.Net.Http.Headers;
using System.Text;
namespace Refresh_Model
{
    internal class Program
    {
        public static HttpClient client = new();
        public static string response = "";
        public static string url = "";
        public static string semanticmodel = "";
        public static string clientId = "";
        public static string workspace = "";
        private static string[] scopes = new string[] { "https://analysis.windows.net/powerbi/api/.default" };
        private static string Authority = "https://login.microsoftonline.com/organizations";
        private static string RedirectURI = "http://localhost";

        static async Task Main(string[] args)
        {
            ReadConfig();
            await RefreshSemanticModel(semanticmodel);
            await RefreshWorkspace();
            await CheckStatus();
        }

        public async static Task<string> RefreshSemanticModel(string SemanticModel)
        {
            response = await GetAsync("https://api.powerbi.com/v1.0/myorg/groups");
            JObject jobject_groups = JObject.Parse(response);
            JArray jArray_groups = (JArray)jobject_groups["value"];
            foreach (JObject path in jArray_groups)
            {
                if (path["name"].ToString() == workspace)
                {
                    url = "https://api.powerbi.com/v1.0/myorg/groups/" + path["id"].ToString();
                    url = url + "/datasets/";
                    response = await GetAsync(url);
                    JObject jobjectdatasets = JObject.Parse(response);
                    JArray jArraydatasets = (JArray)jobjectdatasets["value"];
                    foreach (JObject path_datasets in jArraydatasets)
                    {
                        if (path_datasets["name"].ToString() == SemanticModel)
                        {
                            response = await PostAsyncRefreshPartition(url + path_datasets["id"].ToString() + "/refreshes");
                        }

                    }

                }

                url = "";
            }

            return "Success";
        }

        public async static Task<string> RefreshWorkspace()
        {
            response = await GetAsync("https://api.powerbi.com/v1.0/myorg/groups");
            JObject jobject_groups = JObject.Parse(response);
            JArray jArray_groups = (JArray)jobject_groups["value"];
            foreach (JObject path in jArray_groups)
            {
                if (path["name"].ToString() == workspace)
                {
                    url = "https://api.powerbi.com/v1.0/myorg/groups/" + path["id"].ToString();

                    url = url + "/datasets/";
                    response = await GetAsync(url);
                    JObject jobjectdatasets = JObject.Parse(response);
                    JArray jArraydatasets = (JArray)jobjectdatasets["value"];
                    foreach (JObject path_datasets in jArraydatasets)
                    {
                        response = await PostAsyncRefreshSemanticModel(url + path_datasets["id"].ToString() + "/refreshes");
                    }

                url = "";
            }

            return "Success";
        }

        public async static Task<Dictionary<string, string>> CheckStatus()
        {
            response = await GetAsync("https://api.powerbi.com/v1.0/myorg/groups");
            JObject jobject_groups = JObject.Parse(response);
            JArray jArray_groups = (JArray)jobject_groups["value"];
            Dictionary<string,string> refreshDictionary = new();
            foreach (JObject path in jArray_groups)
            {
                if (path["name"].ToString() == workspace)
                {
                    url = "https://api.powerbi.com/v1.0/myorg/groups/" + path["id"].ToString();
                    url = url + "/datasets/";
                    response = await GetAsync(url);
                    JObject jobjectdatasets = JObject.Parse(response);
                    JArray jArraydatasets = (JArray)jobjectdatasets["value"];
                    foreach (JObject path_datasets in jArraydatasets)
                    {

                        response = await GetAsync(url + path_datasets["id"].ToString() + "/refreshes");
                        JObject JObjectrefreshresponse = JObject.Parse(response);
                        JArray JArrayrefreshresponse = (JArray)JObjectrefreshresponse["value"];
                        foreach(JObject pathrefresh in JArrayrefreshresponse)
                        {

                            refreshDictionary.Add(pathrefresh["requestId"].ToString(), "https://app.powerbi.com/groups/" + path["id"].ToString() + "/datasets/" + path_datasets["id"].ToString() + "/refreshdetails/" + pathrefresh["requestId"].ToString());
                        }


                    }

                }
            }

            return refreshDictionary;
        }

        public static void ReadConfig()
        {
            var builder = new ConfigurationBuilder()
            .AddJsonFile($"local.settings.json", true, true);
            var config = builder.Build();
            clientId = config["ClientId"];
            workspace = config["Workspace"];
            semanticmodel = config["Semantic Model"];
        }

        public async static Task<string> GetAsync(string url)
        {

            AuthenticationResult result = await ReturnAuthenticationResult();
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

            HttpResponseMessage response = await client.GetAsync(url);
            try
            {
                response.EnsureSuccessStatusCode();
                return await response.Content.ReadAsStringAsync();
            }
            catch
            {
                Console.WriteLine(response.Content.ReadAsStringAsync().Result);
                return null;
            }

        }

        public static async Task<string> PostAsyncRefreshSemanticModel(string url)
        {
            AuthenticationResult result = await ReturnAuthenticationResult();
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

            JObject jsonObject = new JObject
            {
                ["notifyOption"] = "MailOnFailure",
                ["retryCount"] = "3"
            };
            HttpRequestMessage httprequestmessage = new HttpRequestMessage
            {
                Method = HttpMethod.Post,
                RequestUri = new Uri(url),
                Content = new StringContent(jsonObject.ToString(), Encoding.UTF8, "application/json")

            };
            HttpResponseMessage response = await client.SendAsync(httprequestmessage);

            response.EnsureSuccessStatusCode();
            await response.Content.ReadAsStringAsync();
            return response.StatusCode.ToString();
        }


        public static async Task<string> PostAsyncRefreshPartition(string url)
        {
            AuthenticationResult result = await ReturnAuthenticationResult();
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

            JObject jsonObject = new JObject
            {
                ["type"] = "Full",
                ["commitMode"] = "transactional",
                ["maxParallelism"] = 2,
                ["retryCount"] = 2,
                ["timeout"] = "02:00:00",
                ["objects"] = new JArray
                {
                    new JObject
                    {
                        ["table"] = "Your Table",
                        ["partition"] = "Your Partition"
                    }
                }
            };
          
            HttpRequestMessage httprequestmessage = new HttpRequestMessage
            {
                Method = HttpMethod.Post,
                RequestUri = new Uri(url),
                Content = new StringContent(jsonObject.ToString(), Encoding.UTF8, "application/json")

            };
            HttpResponseMessage response = await client.SendAsync(httprequestmessage);

            response.EnsureSuccessStatusCode();
            await response.Content.ReadAsStringAsync();
            return response.StatusCode.ToString();
        }

        public async static Task<AuthenticationResult> ReturnAuthenticationResult()
        {
            string AccessToken;
            PublicClientApplicationBuilder PublicClientAppBuilder =
                PublicClientApplicationBuilder.Create(clientId)
                .WithAuthority(Authority)
                .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
                .WithRedirectUri(RedirectURI);

            IPublicClientApplication PublicClientApplication = PublicClientAppBuilder.Build();
            var accounts = await PublicClientApplication.GetAccountsAsync();
            AuthenticationResult result;
            try
            {

                result = await PublicClientApplication.AcquireTokenSilent(scopes, accounts.First())
                                 .ExecuteAsync()
                                 .ConfigureAwait(false);

            }
            catch
            {
                result = await PublicClientApplication.AcquireTokenInteractive(scopes)
                                 .ExecuteAsync()
                                 .ConfigureAwait(false);

            }

            return result;

        }
    }

}
```

### Conclusion

Through this write up I tried to demonstrate how the API’s can be customized for a specific need, whether it requires refresh of a single semantic model or all the semantic models in the given workspace. Also I have highlighted how to leverage the new additions to the recent update of the refresh API’s.

The only missing feature in my opinion is the ability to return the refresh details directly in the response rather than requiring the construction of a URL that points to a new location.

Thanks for reading !!!
