Stop Polling Microsoft Graph: Use Delta Queries to Sync Users Efficiently
Polling /users causes throttling; delta queries sync only changes, cutting API calls and latency.
21 Sept 2025, 05:12 UTC

When an application needs to keep an external directory in sync with Azure AD users, the most common approach is to poll the /users endpoint on a regular schedule. For a small tenant this works, but for tens of thousands of users the request size, network latency, and risk of a 429 (Too Many Requests) response become serious operational concerns.
Thesis: Use Microsoft Graph Delta Queries
Delta queries let you retrieve only the items that have changed since a previous request. The API returns a @odata.deltaLink that acts as a cursor. By storing this link and using it on each subsequent call, you avoid transferring the full user list and dramatically reduce throttling risk.
How Delta Sync Works
A delta query functions like a state token. The first request to /users?$delta returns the current set of users and a link that encodes the current state of the collection.
- The link contains a token that represents the snapshot of the user collection at that moment.
- Subsequent requests use the same link; the service compares the snapshot with the current state and returns only creations, updates, and deletions.
- If no changes occurred, the response body is empty but the same
@odata.deltaLinkis returned for the next check.
Deletion handling is explicit: a deleted user appears in the value array with a deleted property set to true. Your code must remove that record from any local cache.
Practical Implementation Steps
Step 1 – Obtain Initial Baseline
Run a GET request without the delta flag to capture the full list and the first delta token.
GET https://graph.microsoft.com/v1.0/users?$delta
Typical response includes:
{
"value": [
{ "id": "user-123", "displayName": "Alice Doe" },
{ "id": "user-456", "displayName": "Bob Smith" }
],
"@odata.deltaLink": "https://graph.microsoft.com/v1.0/users/delta?token=abc-xyz"
}
Store the @odata.deltaLink value in a persistent store (database, file, or key‑value store). This token is your synchronization cursor.
Step 2 – Perform Incremental Sync
On the next scheduled run, read the stored token and issue a request using it.
GET https://graph.microsoft.com/v1.0/users/delta?token=abc-xyz
Expected response when Alice changes her name and Bob is removed:
{
"value": [
{ "id": "user-123", "displayName": "Alice Jones" },
{ "id": "user-456", "deleted": true }
],
"@odata.deltaLink": "https://graph.microsoft.com/v1.0/users/delta?token=def-789"
}
Process the value array: update the record for Alice, delete the record for Bob from your local store.
Step 3 – Detect Token Expiration
If the Graph API returns a 410 Gone error, the delta token is no longer valid. Your code should catch this error, discard the old token, and issue a fresh /users?$delta request to obtain a new baseline.
Required Permissions and Diagnostics
Delta queries require an app registration with the Directory.Read.All or User.Read.All delegated permission, granted via admin consent. The token you use must be an OAuth2 bearer token with the appropriate scopes.
- Verify the token includes the needed scope by decoding the JWT or inspecting the scopes in your auth library.
- Test the initial request in a tool like Postman; a successful 200 response with a
@odata.deltaLinkconfirms the permission is correct. - If you receive a 403, the scope is missing; if you receive a 401, the token is expired or invalid.
Trade‑offs and Limitations
- State Management: You must persist the delta token and handle the 410 Gone case, adding code complexity.
- Initial Load: The first full sync can be large for tenants with many users, consuming bandwidth and time.
- Entity Support: Not every Microsoft 365 resource supports delta queries (e.g., some groups or device management APIs). Verify the specific endpoint in the Graph API documentation.
Actionable Closing
To adopt delta queries in your integration:
- Register an app with
Directory.Read.All(orUser.Read.All) and obtain an OAuth2 token. - Implement a persistent store for the
@odata.deltaLinkafter the first/users?$deltacall. - Schedule incremental calls using the stored token; on 410, restart the full sync.
- Log the number of items returned per request to monitor throttling; if the count grows unexpectedly, consider back‑off or reduce the polling interval.
By switching from hourly polling to delta‑based sync, you cut API traffic, lower the chance of throttling, and keep your local user cache accurate with minimal overhead.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.