Measure Your Carbon Footprint on Azure Programmatically With the Carbon Optimization REST APIs

The Azure Carbon Optimization REST APIs mark a significant step forward in cloud sustainability, offering an automated way to monitor and manage the carbon emissions from Azure services. This is crucial for organizations aiming to minimize environmental impact and comply with global sustainability standards.

If you haven’t read my post about the new Carbon Optimization Service on Azure, please do so here. In addition to looking at your carbon emissions on the Azure Portal, you can retrieve them programmatically with the REST API, replacing the old Sustainability API I blogged about here. There are several benefits to the new API. First, it uses regular authentication and authorization with RBAC and Microsoft Entra ID, like all the other modern APIs on Azure. All you need to do is:

  • Create a client in Microsoft Entra ID (which is called an App Registration);
  • Make sure the client has the new Carbon Optimization Reader role;
  • Make a note the client ID (application ID on Azure) and the client Secret after creating the client

After that, you’re all set to go! I made a Console App in C#.NET to test the API. To authenticate, we’ll first need the following lines of code:

string authority = "https://login.microsoftonline.com/"+ tenantId;
string[] scopes = new string[] { "https://management.azure.com/.default" };
string[] subscriptions = new string[] {
    "e503055c-913a-4ce1-947c-66c125e273d2"
};

Replace tenantId with your tenant’s ID. Then, we are going to acquire an access token for this client:

var app = ConfidentialClientApplicationBuilder.Create(clientId)
    .WithClientSecret(clientSecret)
    .WithAuthority(authority)
    .Build();

var authenticationResult = await app.AcquireTokenForClient(scopes).ExecuteAsync();

string accessToken = authenticationResult.AccessToken;

Replace clientId and clientSecret with the client ID and client secret of the client you made earlier with the Carbon Optimization Reader role.

After retrieving an access token, we can call the Carbon Optimization API. At the moment of writing this, we have two endpoints:

  • List Carbon Emission Reports
  • Query Carbon Emission Data Available Date Range

Query Carbon Emission Data Available Data Range

It makes sense first to retrieve the available data range for the carbon emission data so we won’t make a call to list the carbon emission reports with an invalid data range. The C# code for this looks like:

using (HttpClient client = new HttpClient())
{
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

    string queryCarbonEmissionDataAvailableDateRangeUrl = "https://management.azure.com/providers/Microsoft.Carbon/queryCarbonEmissionDataAvailableDateRange?api-version=2023-04-01-preview";
    HttpResponseMessage response = await client.PostAsync(queryCarbonEmissionDataAvailableDateRangeUrl, null);

    string responseContent = await response.Content.ReadAsStringAsync();
    Console.WriteLine(responseContent);
}

Note how the access token we retrieved earlier is used in the authentication header as a bearer token. Also, note how the URL begins with the general URL “https://management.azure.com/providers/” then specifies the provider “Microsoft. Carbon/”, then selects the endpoint “queryCarbonEmissionDataAvailableDateRange” and how it finishes with the query parameter “api-version=2023-04-01-preview”. So bear in mind this is all in preview and could change shortly. Observe it’s a POST method. When we execute this POST call on the endpoint via the HTTP client, we get the following response:

{
    "endDate": "2024-01-01",
    "startDate": "2022-02-01"
}

List Carbon Emission Reports

Let’s use this data range in the subsequent call to obtain detailed resource-level emission data. I am most interested in this call because previously, it wasn’t possible to get carbon emission data on the resource level with the Sustainability API. This would look in C# like:

DateRangeResponse dateRange = JsonConvert.DeserializeObject<DateRangeResponse>(responseContent);

DateRangeRequest dateRangeRequest = new DateRangeRequest
{
    StartDate = dateRange.StartDate,
    EndDate = dateRange.EndDate
};

ItemDetailsQueryFilter itemDetailsQueryFilter = new ItemDetailsQueryFilter
{
    CarbonScopeList = [EmissionScopeEnum.Scope1.ToString(), EmissionScopeEnum.Scope2.ToString(), 
    EmissionScopeEnum.Scope3.ToString()],
    CategoryType = CategoryTypeEnum.Resource.ToString(),
    DateRange = dateRangeRequest,
    OrderBy = OrderByEnum.TotalCarbonEmission.ToString(),
    PageSize = 10,
    ReportType = ReportTypeEnum.ItemDetailReport.ToString(),
    ResourceGroupUrlList = Array.Empty<string>(),
    SortDirection = SortDirectionEnum.Asc.ToString(),
    SubscriptionList = subscriptions
};

string itemDetailsQueryFilterAsJson = JsonConvert.SerializeObject(itemDetailsQueryFilter);
StringContent itemDetailsQueryFilterAsStringContent = new 
    StringContent(itemDetailsQueryFilterAsJson, Encoding.UTF8, "application/json");

string carbonUrl = 
    "https://management.azure.com/providers/Microsoft.Carbon/carbonEmissionReports?api- 
    version=2023-04-01-preview";
HttpResponseMessage postResponse = await client.PostAsync(carbonUrl, 
    itemDetailsQueryFilterAsStringContent);

string postResponseContent = await postResponse.Content.ReadAsStringAsync();
Console.WriteLine(postResponseContent);

First the DataRangeResponse object is turned into a DataRangeRequest object. Because if you look at the DataRangeResponse object is looks like this:

    internal class DateRangeResponse
    {
        [JsonProperty("startDate")]
        public string StartDate { get; set; }
        [JsonProperty("endDate")]
        public string EndDate { get; set; }
    }

And if you look at the DataRangeRequest object it looks like this:

    internal class DateRangeRequest
    {
        [JsonProperty("start")]
        public string StartDate { get; set; }
        [JsonProperty("end")]
        public string EndDate { get; set; }
    }

Please note the difference in the JSON properties, startDate vs. start and endDate vs. end.

With the proper DataRangeRequest object we can construct the ItemDetailsQueryFilter object, which looks like this:

internal class ItemDetailsQueryFilter
{
    [JsonProperty("carbonScopeList")]
    public string[] CarbonScopeList { get; set; }

    [JsonProperty("categoryType")]
    public string CategoryType { get; set; }

    [JsonProperty("dateRange")]
    public DateRangeRequest DateRange { get; set; }

    //[JsonProperty("groupCategory")]
    //public string GroupCategory { get; set; }

    [JsonProperty("orderBy")]
    public string OrderBy { get; set; }

    [JsonProperty("pageSize")]
    public int PageSize { get; set; }

    [JsonProperty("reportType")]
    public string ReportType { get; set; }

    [JsonProperty("sortDirection")]
    public string SortDirection { get; set; }

    [JsonProperty("subscriptionList")]
    public string[] SubscriptionList { get; set; }

    [JsonProperty("resourceGroupUrlList")]
    public string[] ResourceGroupUrlList { get; set; }

    [JsonProperty("skipToken")]
    public string SkipToken { get; set; }
}

Please note that the group category is commented out. I got an error when I tried to pass it along empty (or with valid values, so I thought). But you can ignore the group category name here because this is ResourceItemDetailsData, which I learned from Microsoft. Other flavors where this value will be relevant would be when you are grouping by Subscription, ResourceGroup, and so on.

Also, note that Skiptoken is ignored for now, but you can use this to get to the next page; see page size.

The enumerations used to construct the ItemDetailsQueryFilter object are

    internal enum EmissionScopeEnum
    {
        Scope1,
        Scope2,
        Scope3
    }

    internal enum CategoryTypeEnum
    {
        Location,
        Resource,
        ResourceGroup,
        ServiceType,
        Subscription
    }

    internal enum SortDirectionEnum
    {
        Asc,
        Desc
    }

    internal enum OrderByEnum
    {
        Name,
        TotalCarbonEmission,
        TotalCarbonEmissionLastMonth,
        SubscriptionId
    }

    internal enum ReportTypeEnum
    {
        ItemDetailReport,
        OverallSummaryReport
    }

    internal enum GroupCategoryEnum
    {
        ResourceGroup,
        SubscriptionId
    }

Note how it is ItemDetailReport for ReportTypeEnum and not ItemDetailsReport (see the missing ‘s’).

Full descriptions for the attributes of the ItemDetailsQueryFilter are as follows

  • carbonScopeList (Required, EmissionScopeEnum[]): This array defines the scopes of carbon emissions to be included in the query. Carbon emission scopes typically have Scope 1 (direct emissions from owned or controlled sources), Scope 2 (indirect emissions from the generation of purchased energy), and Scope 3 (all other indirect emissions that occur in a company’s value chain). Developers need to specify which of these scopes they want to include in the emissions data retrieval, ensuring the data reflects the specific environmental impact areas relevant to their analysis.
  • categoryType (Required, CategoryTypeEnum): This property indicates the type of item categories to consider in the emissions data report. The category type could range from resource types, service types, locations, etc. It helps categorize the emissions data based on the specified dimension, providing a clearer view of which areas contribute to the carbon footprint, thus allowing for targeted sustainability efforts.
  • dateRange (Required, DateRange): Specifies the start and end dates for the period over which carbon emissions data should be retrieved. This allows developers to define a specific time frame for analysis, enabling the tracking of emissions over time, identifying trends, and evaluating the effectiveness of carbon reduction strategies.
  • group category (Required, string): Defines the group category name for which the emissions data should be aggregated. This could be a resource group, service category, or other relevant grouping criterion. It allows for more granular analysis within the broader category types, aiding in pinpointing specific areas of high emissions within a category.
  • orderBy (Required, string): Determines the column name by which the returned items should be ordered. This could refer to any relevant data field, such as total emissions, service name, etc. Calling the results makes the data more readable and helps quickly identify the most significant contributors to the carbon footprint.
  • pageSize (Required, integer): Dictates the number of items to return in one response. This allows developers to manage the volume of data produced by the API, facilitating easier data handling and analysis, especially when dealing with large sets of emissions data.
  • reportType (Required, string: ItemDetailReport): Specifies the type of report to generate. In this case, the report type is set to ItemDetailReport (not ItemDetailsReport), which indicates that the query aims to retrieve detailed carbon emissions data. This report type is crucial for deep-diving into specific items or categories to understand their carbon emission levels.
  • sortDirection (Required, string): Specifies the direction of sorting for the query results, such as ascending or descending. This affects the order in which items are presented in the report, aiding in data interpretation by highlighting the highest or lowest emitters based on the chosen sort criterion.
  • subscriptionList (Required, string[]): An array of subscription IDs for which carbon emissions data should be retrieved. This allows filtering the data to include only specific Azure subscriptions, making the analysis relevant to the selected cloud resources and services.
  • resourceGroupUrlList (Optional, string[]): This optional array specifies the resource group URLs for retrieving emissions data. Including specific resource groups can further narrow the data retrieval to pertinent areas, enabling a more focused analysis of particular segments of an Azure subscription.
  • skipToken (Optional, string): This is used for pagination; it specifies the number of result items to skip. This is particularly useful when dealing with large datasets, as it allows developers to navigate through the results incrementally, retrieving manageable chunks of data at a time.

We proceed by calling the URL: https://management.azure.com/providers/Microsoft.Carbon/carbonEmissionReports?api-version=2023-04-01-preview, which is a POST method again. It expects different attributes based on the value of the report type. In our case, we specified ItemDetailReport and passed the attributes with appropriate values for these attributes. And after we’ve called the client, we receive… an error?

At the moment of writing this blogpost, the API does not support multiple months yet for the ItemDetailReport. So we’ll have to rewrite the piece of code where the DateRangeRequest object was created as follows:

    DateRangeRequest dateRangeRequest = new DateRangeRequest
    {
        StartDate = "2024-01-01",
        EndDate = "2024-01-01"
    };

This will give us detailed emission data on resource level for January 2024. Adjust this for a month you have carbon emission data for. This probably has to do with the fact that carbon emission data is basically only per month and is even a month behind. I hope Microsoft will improve this shortly.

For now, after we have applied this work-around we receive the following response:

{
    "skipToken": "",
    "value": [
        {
            "categoryType": "Resource",
            "changeRatioFor12Months": 0,
            "changeRatioForLastMonth": 0,
            "changeValueMonthOverMonth": 0.157786430917075,
            "dataType": "ResourceItemDetailsData",
            "groupName": "",
            "itemName": "dirtyvm",
            "resourceGroup": "dirty-console-app-rg",
            "resourceId": "/subscriptions/e503055c-913a-4ce1-947c-66c125e273d2/resourcegroups/dirty-console-app-rg/providers/microsoft.compute/virtualmachines/dirtyvm",
            "resourceType": "microsoft.compute/virtualmachines",
            "subscriptionId": "e503055c-913a-4ce1-947c-66c125e273d2",
            "totalCarbonEmission": 0.157786430917075,
            "totalCarbonEmission12MonthsAgo": 0,
            "totalCarbonEmissionLastMonth": 0
        }
    ]
}

The C# class for the collection class looks like:

internal class CarbonEmissionDataListResult
 {
     [JsonProperty("skipToken")]
     public string SkipToken { get; set; }
     [JsonProperty("value")]
     ResourceItemDetailsData[] Value { get; set; }
 }

As you can see, an array of CarbonEmissionDataListResult class objects are in the attribute “value”. The documentation says it should also contain a “nextLink”. I did not see a “nextLink” in my test. The C# class for the detail objects looks like this:

public class CarbonEmissionItemDetailData
{
    [JsonProperty("dataType")]
    public string DataType { get; set; }

    [JsonProperty("itemName")]
    public string ItemName { get; set; }

    [JsonProperty("categoryType")]
    public string CategoryType { get; set; }

    [JsonProperty("groupName")]
    public string GroupName { get; set; }

    [JsonProperty("subscriptionId")]
    public string SubscriptionId { get; set; }

    [JsonProperty("resourceGroup")]
    public string ResourceGroup { get; set; }

    [JsonProperty("resourceId")]
    public string ResourceId { get; set; }

    [JsonProperty("resourceType")]
    public string ResourceType { get; set; }

    [JsonProperty("totalCarbonEmission")]
    public double TotalCarbonEmission { get; set; }

    [JsonProperty("totalCarbonEmission12MonthsAgo")]
    public double TotalCarbonEmission12MonthsAgo { get; set; }

    [JsonProperty("totalCarbonEmissionLastMonth")]
    public double TotalCarbonEmissionLastMonth { get; set; }

    [JsonProperty("changeRatioFor12Months")]
    public double ChangeRatioFor12Months { get; set; }

    [JsonProperty("changeRatioForLastMonth")]
    public double ChangeRatioForLastMonth { get; set; }

    [JsonProperty("changeValueMonthOverMonth")]
    public double ChangeValueMonthOverMonth { get; set; }
}

Please note that in my tests at data type, it says ResourceItemDetailsData, which is not the same as in the documentation. The details of the attributes are as follows:

  • categoryType (Required, CategoryTypeEnum): This property specifies the item’s category being analyzed for carbon emissions. The category helps segment the data according to different aspects of Azure usage, such as by service type, resource, etc. Understanding the category is crucial for developers to pinpoint areas where carbon emissions can be reduced.
  • changeRatioFor12Months (Required, number): This numerical value represents the percentage change in carbon emissions for the specified item or category compared to its value 12 months ago. It helps developers assess the long-term effectiveness of their carbon reduction strategies by comparing current performance to past data.
  • changeRatioForLastMonth (Required, number): Similar to the changeRatioFor12Months, this property shows the percentage change in carbon emissions compared to the previous month. It provides insights into the short-term impact of changes made to Azure resources or usage patterns, helping developers quickly adapt and improve their sustainability efforts.
  • changeValueMonthOverMonth (Required, number): This property indicates the actual difference in total carbon emissions between the current reporting period and the last month. It offers a precise, immediate measure of the effect of recent changes, enabling developers to quantify the impact of their recent adjustments or interventions.
  • dataType (Required, string: ItemDetailsData): Identifies the data type returned by the query. This structure should always be set to ‘ItemDetailsData,’ signifying that the data pertains to detailed insights into the carbon emissions of specific Azure resources or services.
  • groupName (Required, string): Defines the name of the group associated with the item, which could be a resource group or any other organizational unit within Azure. This classification helps organize and contextualize the carbon emissions data within specific segments of an organization’s Azure infrastructure.
  • itemName (Required, string): Specifies the name of the item for which carbon emissions data is being reported. Typically, this refers to an individual Azure resource for this structure, enabling developers to identify and focus on high-emission areas within their cloud infrastructure.
  • totalCarbonEmission (Required, number): Reflects the total carbon emissions associated with the item in question, based on the provided query parameters. This figure is vital for understanding the overall environmental impact of specific Azure services or resources.
  • totalCarbonEmission12MonthsAgo (Required, number): Gives the total carbon emissions for the item as they were 12 months prior, allowing developers to compare current emissions data with historical figures to assess long-term trends and the effectiveness of emission reduction strategies over the past year.
  • totalCarbonEmissionLastMonth (Required, number): Provides the total carbon emissions for the item for the last month. This enables a month-over-month comparison, helping developers to understand recent trends and the immediate effects of any changes in Azure resource usage or optimization efforts.

Conclusion

In summary, leveraging the Azure Carbon Optimization REST APIs enables developers and organizations to monitor and reduce their cloud carbon emissions programmatically. This aligns with global sustainability efforts and can contribute significantly to an organization’s environmental responsibility goals. Integrating these APIs enables you to automate emissions tracking, leading to informed decisions and sustainable cloud usage practices.

Further documentation and resources can be found in Microsoft’s official documentation for developers looking to dive deeper or implement these APIs. This will provide additional insights, best practices, and technical guidance to utilize the Carbon Optimization Service effectively within your Azure environment.

Remember, sustainable cloud management benefits the environment and can result in cost savings and operations.

About Danny

Passionate about cloud-native development on the Azure cloud. Focusses on sustainable software engineering and how to get it out there in the world. Got a lovely wife, 2 kids, a cat and crazy expensive hobbies like Warhammer AoS and 40k.
This entry was posted in .NET programming, azure and tagged , , , , , , . Bookmark the permalink.

Leave a comment