blog.atwork.at

news and know-how about microsoft, technology, cloud and more.

Taming the 429: How we analyzed Microsoft Graph API throttling and developed a workaround

If you've worked extensively with the Microsoft Graph API, you've likely encountered the dreaded HTTP 429 Too Many Requests error. It's a clear indication that you've hit a rate limit - but it's not always clear why, when, or how to work around the problem. In this post, we'll share our real-world experience analyzing and overcoming 429 responses while querying user data through Graph.

In this article, we share our joint findings and real-world experience analyzing and overcoming 429 responses while querying user data through Graph REST API. Big thanks to my colleague Andi for testing and helping uncover these insights.

The Goal

We are authenticating as an app and want to iterate through all users in a Microsoft 365 tenant to retrieve specific user data for further processing. For each user, we run a Graph REST query like this:

https://graph.microsoft.com/v1.0/users/<user-id>?$select=id,displayName,userPrincipalName,signInActivity

The Error

When performing this operation across multiple users, we encountered HTTP 429 Too Many Requests error. These requests had previously worked without issue, which led us to believe that Microsoft has recently strengthened its rate limiting (or metering) policies. Our goal was to retrieve specific user properties using a single Graph API request per user  - a straightforward approach that suddenly began triggering throttling responses. However, we conducted tests to see if we could identify a viable workaround.

Testing Scenarios to Trigger an HTTP 429 Error

For quick and simple testing, we used a PowerShell script. We authenticated using an app token, which we stored in the $script:APIHeader variable. The script included a loop with n repetitions to repeatedly read the same user, simulating a high-frequency access pattern. Here is the testing script (TestGraphRequest429.ps1). In a real-world scenario, we requested around 30 user properties, but for simplicity and clarity, we've streamlined the request in this example with just 4 properties.

# Testing Graph Requests with a specific user
$UserId = "1234301f-1222-4466-a98c-e13c022d7abc"
# Get one specific user and some user properties by ID
$Uri = "https://graph.microsoft.com/v1.0/users/$($UserId)?`$select=id,displayName,userPrincipalName,signInActivity"

# Testing many requests
for ($i = 1; $i -le 30; $i++) {
    $response = Invoke-RestMethod -Method Get -Uri $Uri -Headers $script:APIHeader `
        -SkipHttpErrorCheck -StatusCodeVariable statusCode
    if ($statusCode -eq 429) {
        Write-Host "$i. Too Many Requests: Retrying after delay. statusCode: $($statusCode)" -ForegroundColor Red
        break # Exit the loop after the first 429 error for testing purposes
    }
    else {
        Write-Host "$i. $($response.displayName), $($response.signInActivity.lastSignInDateTime). statusCode: $($statusCode)" -ForegroundColor Green
    }
}

When we execute the script, we typically receive an HTTP 429 error - usually after 15 to 20 requests.

This shouldn't be a problem, but the error still occurs reproducibly. The output looked like here:

1. Adele Vance, 04/07/2025 08:44:51. statusCode: 200
2. Adele Vance, 04/07/2025 08:44:51. statusCode: 200
...
15. Adele Vance, 04/07/2025 08:44:51. statusCode: 200
16. Adele Vance, 04/07/2025 08:44:51. statusCode: 200
17. Too Many Requests: Retrying after delay. statusCode: 429

So, we hit the 429 error after a relatively small number of requests.

The Why

We wanted to find out if we can influence that result somehow and whether this also happens with "standard" user properties, We remembered from the early days of working with Microsoft Graph that it's sometimes possible to inspect the underlying API calls for certain methods. So, we tried using the $whatif parameter in our request to see what was happening behind the scenes.

https://graph.microsoft.com/v1.0/users/<user-id>?$select=id,displayName,userPrincipalName,signInActivity&$whatif

We ran that query in Graph Explorer which is very helpful for quick tests.

image

Oh! We noticed that one user property  - signInActivity - is retrieved from a different endpoint. Could this additional Request2 be the reason behind our HTTP 429 issue?

The workaround

With that knowledge, we modified the $Uri request and removed the signInActivity, as shown below:

$Uri = "https://graph.microsoft.com/v1.0/users/$($UserId)?`$select=id,displayName,userPrincipalName"

Yes! When we ran the simplified script, it executed successfully - all 30 repetitions completed without any issues. This result was also reproducible and worked successfully with 100 repetitions as well.

So, the workaround for this scenario is to remove the signInActivity property from the original request to avoid the additional call that appears to trigger the HTTP 429 error. If we truly need the signInActivity data, we'll have to retrieve it through separate, dedicated requests.

In retrospect, it may seem simple, but identifying the root cause, designing a targeted test, and finding a reliable workaround took time and careful analysis.

Summary

If you encounter HTTP 429 errors and can't apply the same analysis and workaround we used, be sure to check out Microsoft's official Microsoft Graph throttling guidance. It's important to respect the Retry-After response header and implement proper retry logic - as the Microsoft Graph SDKs do - to ensure reliable data access.

In our case, we were unable to use the Graph SDK for certain reasons and switched to the REST method in our app, which caused the problems mentioned above but also provided the answer for the throttling.

We hope sharing our experience helps you avoid the same pitfalls and saves you valuable time with Graph requests.

Loading