Azure Carbon Optimization exploration and Postman collection

Earlier this year I reached out to a few different CMS companies to ask if they had any plans to provide SCI metrics for their SaaS products. Alas, I found very little evidence of any SaaS companies providing SCI metrics. Lately, I've been thinking "if I haven't done this myself, then do I have the right to ask someone else for it?"

My ultimate goal is to calculate an SCI metric for my blog and honestly, right now, I have no idea what the energy/carbon consumption of my site is per page requested. No idea whatsoever! Before I do this, I need to take some measurements. This article explains how I used the Carbon Optimization page in Azure to get an insight into the energy usage of my blog. What I found was odd: every single month showed exactly the same figure, 0.1 kgCO2e. That seemed too tidy to be real, so I decided to dig deeper using the Carbon Optimization API to get a more accurate picture. I will share with you the process I used to do this and a Postman collection you can use to query your own Azure resources.

Carbon Optimization page in Azure Portal

If you've never accessed this from your Azure Portal before, go to the search bar and start typing "carbon optimization" and it will come up in the listed services. After you have used it, you should see the shortcut icon on your home screen in the portal.

Examining stats and some quirks of my low-traffic app

My first stop was the "Emission Trends" page. What really jumped out at me was that every month seems to show exactly 0.1 kgCO2 emissions. This seemed suspicious to me. I hit the "Export to CSV" button and the results show the exact same values, so this export contains rounded values, not raw ones. It's worth noting that my blog receives relatively low traffic, so would not actually generate that much usage/load.

The second thing, was the values on the y-axis of the graph: 0, 0, 0, 0, 0, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1. This has a fairly simple explanation; the values are rounded to 1 dp.

An interesting point about the CSV export, not shown on the graph, is the Carbon Intensity. This is the last column on the CSV screenshot above. Carbon intensity is the amount of greenhouse gas emissions per unit of normalized output. Confused? So am I! It's described here https://learn.microsoft.com/en-us/azure/carbon-optimization/emissions-terminology quote:

Your company’s allocation of Microsoft’s carbon emissions divided by your company’s cloud usage hours during the selected time period. Usage hours are based on a sum of your company’s compute, storage, and data transfer in the Microsoft cloud. Usage for emissions calculations might not equal your Microsoft usage for billing purposes.

In rough terms: how much CO2 is being generated per hour of usage

Interestingly, the carbon intensity in the report is steadily falling over time. This could be attributed to a few things:

  • UK grid decarbonisation (more solar and wind, less coal)

  • Microsoft's rewable energy procurement (Microsoft buying PPAs or renewable energy credits to reduce their carbon footprint)

  • Microsoft's improvements to their Datacentres (better PUE, more efficient hardware etc)

  • Changing emissions calculation methodology

One thing to note is that Microsoft do not state if their calculations use global emissions, or locally calculated. This could theoretically be tested by running an identical load test in two different regions and comparing the results.

Azure Carbon Optimization API

In order to utilise the API, it is necessary to configure the necessary authorization in Azure and secondly have a means to make requests to the API - for this I will show how to set up and use Postman.

Azure Configuration Steps

Part 1: Create App Registration (and Service Principal)

  1. Go to Entra ID. In the Azure portal, search for "Microsoft Entra ID" in the top search bar and select it.

  2. Open App registrations. In the left menu, select App registrations, then click + New registration.

  3. Fill in the registration form:

    • Name: eg my-app-carbon-reader (note this as you will need it later)

    • Supported account types: choose Accounts in this organizational directory only (single tenant) unless you have a specific reason for multi-tenant

    • Redirect URI: leave blank

    • Click Register.

  4. On the app's Overview page (you'll land here right after registering), copy and save

    • Application (client) ID

    • Directory (tenant) ID

  5. Create the client secret: in the left menu, select Certificates & secrets Client secrets tab → + New client secret

    • Give it a description (e.g. carbon-api-access) and choose an expiry.

    • Click Add -> Immediately copy the secret's Value (not the Secret ID) — it's shown only once. If you navigate away before copying it, you'll have to delete it and create a new one.

Part 2: Assign Service Principal to a Subscription

  1. In the Azure portal, go to Subscriptions and select the subscription your app's resources live in (copy the Subscriptio ID).

    • Select Access control (IAM) in the left menu.

    • Click + AddAdd role assignment

    • On the Role tab, search for and select Carbon Optimization Reader, then click Next.

    • On the Members tab -> Assign access to: User, group, or service principal

      • Click + Select members, search for the app registration name you created in step 3 (e.g. my-app-carbon-reader) — it'll appear as a service principal, not a user

      • Select it, click Select

    • Click Review + assign (twice — there's a review step, then a final confirm).

From this, you should have saved the following values:

  1. Application (client) ID

  2. Directory (tenant) ID

  3. Client Secret

  4. Subscription ID

Postman Steps

Install

If you don't have it already, go to https://www.postman.com/ then dowload and install. Please note, you are looking for this postman

and not this one ;-)

Get Collection/Environment from my Git repo

https://github.com/dresser/azure-carbon-optimization-postman

This repo is small. It contains two files of interest: a postman collection ("Azure Carbon Optimization API.postman_collection.json") and a template environment ("Azure Example.postman_environment.json "). You can just download them directly from GitHub if you don't want to check out the repository.

In Postman click "Use resources or import...". Here you can just select the two files mentioned above and import them.

Configure the Postman Environment

An environment in Postman is a collection of values which relate to a specific environent of your app. The "Azure Example" environment will have empty values when you import, so you need to populate this with the 4 values you saved earlier. You also need to choose which resource group you want to examine with the API and for this you need the name of the resource group.

Create a new Access Token

To be able to call the API, Postman needs an access token. To generate this:

  1. Select the collection in Postman (choosing Azure Carbon Optimization API)

  2. Click the Authorization tab and scroll to the bottom of the page until you see the orange "Get new access token" button

  3. Click the button, you should see a success message and a green tick, then on the next screen click "Use Token".

You are now ready to start using the requests to retrieve the carbon optimization data from Azure.

Running the different Reports

There are 5 different reports available from the API. The Microsoft reference is here: https://learn.microsoft.com/en-us/azure/carbon-optimization/api-export-data#report-types but I will also describe how to run them from Postman and a bit of (human-readable) info about them ;-)

To run a report, first choose your start and end dates. Select the collection "Azure Carbon Optimization API" in Postman, then click "Variables" and set appropriate values for startDate and endDate. These should be in the format yyyy-mm-dd. Then select the report on the left pane and then click "Send". You will see the response from the API which is in JSON format.

General notes about the reports
  • The emissions values are in kgCO2E

  • You will see latestMonthEmissions and previousMonthEmissions regardless of the start and end dates you choose. If you choose a 2 day range, the previousMonthEmissions will be from a 2 day range, one month earlier.

  • monthOverMonthEmissionsChangeRatio is calculated as (latestMonthEmissions−previousMonthEmissions)/previousMonthEmissions. So if emissions last month were 1 (kgCO2E) and this month are 2, then we would have (2-1)/1 = 1 i.e. a 100% increase over the previous month. Its a ratio number, so simply multiply by 100 if you want it as a percentage.

  • There is a time lag in emissions data availability. You can't see a month's data until the 19th of the following month. If you set a date range which is too recent, the API returns a "bad request" error.

OverallSummaryReport

Gives total carbon emissions in kgCO2E for the given time period.

Microsoft's description:

Provides total carbon emissions for the specified date range and query parameters, and comparative values for a high-level overview. This report type accepts different values in the start and end fields within DateRange. For example, start: 2024-03-01 and end: 2024-06-01.

MonthlySummaryReport

Gives a month by month output of carbon emissions in kgCO2E. Each month covered by the report gets its own sumary of data.

Microsoft's description:

Provides carbon emissions data by month for the specified query parameters. This report type accepts different values in the start and end fields within DateRange. For example, start: 2024-03-01 and end: 2024-06-01.

TopItemsSummaryReport

Returns the top 10 highest emitting items in your resource group (RG) for a given month. Note if you have less than 10 items in your RG, then it will show fewer results. For each of the items listed, you get "itemName" which is the type of resource (not the name, anoyingly!). My RG contains an "App Service" which shows here as "microsoft.web/sites".

You should set startDate and endDate to the same value to run this report, otherwise you will get a "400 Bad request" error. The error message is quite helpful in guiding you how to fix the problem.

Microsoft's description:

Provides the N highest-emitting items for the specified query filters. This report returns data for a single month at a time, so it requires the same values for the start and end fields within DateRange. A maximum of N=10 items are returned at a time.

TopItemsMonthlySummaryReport

Similar to TopItemsSummaryReport above but you can use different start and end dates to get the top items for each month, for a number of months. Interestingly, this report includes more information about the Azure resources returned, namely "resourceId", "subscriptionId" and "resourceGroup". The previosly mentioned "itemName" now actually returns the name of the resource item instead of its type.

Microsoft's description:

Provides the N highest-emitting items by month for the specified query filter. Returns emissions data for the top N items by month within the given date range. A maximum of N=10 items are returned at a time.

ItemDetailsReport

Emissions for all available resources for the specified month. This report contains more detailed information (like TopItemsMonthlySummaryReport) so "resourceId", "subscriptionId" and "resourceGroup".

Again you need to set startDate and endDate to the same value to run this report, otherwise you will get a "400 Bad request" error.

Microsoft's description:

A granular list of items based on the specified CategoryType (including: Resource, ResourceGroup, ResourceType, Location, or Subscription) for the query filter. You can query for only one month at a time. The DateRange requires the same values for the start and end fields.

So, what are the emissions from my website then?

For the month of July 2026 my blog used a grand total of 0.0702903139125519 kgCO2E or 70.3 gCO2E - equivalent to an average banana!

I'm not going to attempt calculating an SCI score here as some more research is needed. Here's some factors that start to open up the myriad questions and areas for research:

  • Google Analytics (GA) shows my blog had 548 page views in July. A crude calculation shows 0.128 gCO2E per page view, but this is almost meaningless as the following facts demonstrate.

  • Cloudflare (my chosen Content Delivery Network CDN) shows 217.78k requests (not page views) for the previous 30 days (all I can view via the web portal). Note here we are talking requests, so that can include images, robots.txt, JavaScript, CSS, favicon etc which can easily add an additional 20-80 requests for each page load. A few things that push up the Cloudflare values vs GA:

    • Bots and crawler traffic, including AI training crawlers

    • Vulnerability scanners and malicious bots

    • Ad blockers/consent-based GA blocking - this would prevent GA from firing (so lower page views in GA) but would still register in Cloudflare statistics.

  • These stats and the Azure reported emissions do not distinguish page view activity from publishing and authoring activity. When I publish a page in Contentful, it fires a webhook which then tells the site to clear the cache and re-request the content from Contentful. This is not energy/work which actively contributes to serving pages to users.

  • I don't capture or factor in the emissions from Contentful or Cloudflare in what I've looked at so far. This is acceptable as per SCI metric calculation as this is a decision I make on where to define my boundary. It's ok as long as I disclose that boundary.

If there is a month with low traffic and a lot of publishing this means more energy is spent updating content and less on serving pages meaning emissions per page view are increased.

If my blog's traffic is low, the proportion of page views used by bots/crawlers could be higher than that of actual end users. Does this matter? Do we care about the net effort and emissions from all website platform activities from crawlers when calculating the cost per human page view? Please feel free to leave a comment below as I would welcome some input and discussion on this point.

Tech stack details of my blog:

  • Uses Contentful as the CMS

  • The head is built in .NET 10 MVC

  • Hosted in Azure on a Linux App Service

  • Uses Cloudflare CDN

Next steps

My next steps will be to try and work out an SCI metric for my blog - I might even embed it as a widget to display the (most) current data, but that will be for another article!

I also plan to spin-up another instance of the front-end for this blog website, to generate emissions data from a precise (and repeatable) load test. After that, I will spin-up other instances using different technology stacks to compare and contrast the emissions stats from different stacks.

Final thoughts

Hopefully the information here will help enable you to start investigating and understanding the emissions resulting from your apps and services. Thank you for reading, and please feel to leave a comment below if you want more explanation about anything or have any questions, suggestions or corrections.

comments powered by Disqus