Differential Query in Windows Azure Active Directory Graph

In this post we will talk about Differential Query in Graph.

What is Differential Query

Differential Query allows Apps to query for changes that occurred on a given tenant in AAD.

This functionality is especially useful when an App needs to track changes that occurred on tenant in AAD in order to perform App specific operations. It also allows Apps which implement it own data store to effectively integrate with AAD by synchronizing changes from AAD to App’s data store.

How to start using Differential Query

As a starting point, App needs to learn about all changes occurred on a given tenant since the tenant was created in AAD. Here is how an App can perform initial Differential Query request:

  GET https://graph.windows.net/contoso.com/directoryObjects?api-version=2013-04-05&deltaLink=

deltaLink parameter indicates this request to be a Differential Query. And deltaLink parameter with no assigned value indicates that this is an initial Differential Query request.

Differential Query paged responses contain all changes that occurred on the tenant until last page of changes is returned:

  “odata.metadata”: “https://graph.windows.net/contoso.com/$metadata#directoryObjects“,

  # This is the nextLink to be used for the next query
  “aad.nextLink”: “https://graph.windows.net/contoso.com/directoryObjects?deltaLink=XARBN7ivjcS6QIhSZDQR3OkT15SO1eeY-01BZSS0sOct6sh5oEyqOLLKRVhRmBMLHhePUF… [Truncated]”,
  “value”: [

    # User object for John Smith
      “odata.type”: “Microsoft.WindowsAzure.ActiveDirectory.User”,
      “objectType”: “User”,
      “objectId”: “dca803ab-bf26-4753-bf20-e1c56a9c34e2”,
      “accountEnabled”: true,
      “displayName”: “John Smith”,
      “givenName”: “John”,
      “mailNickname”: “johnsmith”,
      “passwordPolicies”: “None”,
      “surname”: “Smith”,
      “usageLocation”: “US”,
      “userPrincipalName”: “johnsmith@contoso.com

aad.nextLink property indicates that there are more changes on the tenant and that the App should perform subsequent Differential Query request right away and pass aad.nextLink property value from the response as deltaLink parameter value (note that api-version needs to be appended as a request parameter):

  GET https://graph.windows.net/contoso.com/directoryObjects?api-version=2013-04-05&deltaLink=XARBN7ivjcS6QIhSZDQR3OkT15SO1eeY-01BZSS0sOct6sh5oEyqOLLKRVhRmBMLHhePUF… [Truncated] 

If a response contains aad.deltaLink property, it indicates that no more changes exist on the AAD tenant in AAD.

  “odata.metadata”: “https://graph.windows.net/contoso.com/$metadata#directoryObjects“,

  # This is the deltaLink to be used for the next query
  “aad.deltaLink”: “https://graph.windows.net/contoso.com/directoryObjects?deltaLink=2O3f9hvaK62A-wIr-oj5sAUMnVNv21k-kIPYZymYDryWzrd0sQrB72EX6TpzPBep2n8v… [Truncated]”,
  “value”: [

    # Group object for IT Administrators
      “odata.type”: “Microsoft.WindowsAzure.ActiveDirectory.Group”,
      “objectType”: “Group”,
      “objectId”: “7373b0af-d462-406e-ad26-f2bc96d823d8”,
      “description”: “IT Administrators”,
      “displayName”: “Administrators”,
      “mailNickname”: “Administrators”,
      “mailEnabled”: false,
      “securityEnabled”: true

Subsequent Differential Query response will contain no changed entities until more changes are made on the tenant. At this point, App should store aad.deltaLink value locally and use it in subsequent Differential Query request. This allows the App to come back after a certain interval and issue incremental Differential Query request. Incremental Differential Query request can be repeated for as long as the App is interested to learn about changes on the tenant (and is authorized to provide services on this tenant).

Differential Query response semantics

There are some key differences in the response structure that set Differential Query apart from regular Graph responses we covered in previous posts:

  • response includes deleted objects and deleted links (indicated by “aad.isDeleted” property with value set to true); this is necessary to make sure App can learn about deletes of previously created objects and links
  • response contains entities with only changed properties; e.g. for a new user created since the last time Differential Query request was made, response only includes new user entity with only properties that were set
  • links represented as entities (DirectoryLinkChange entity type); this allows response to contain only changed links. This allows changes on links to be expressed most efficiently so that, for example, if group with large number of member links was changed to add new member, only this new link could be included in Differential Query response.

Differential Query invariants

App developers should consider the following when implementing Differential Query:

  • changes returned by Differential Query represent the state of AAD objects at the time of response
  • changed objects appear approximately in the order in which they occurred in AAD with most-recently changed objects appearing last. This makes it possible for changes to be presented out of order compared to how they initially occurred in AAD
  • App should be prepared to handle a deletion change for an object it was not aware of
  • Differential Query can return a link to a source or target object that has not yet been previously returned
  • App should be prepared for receiving same change in a subsequent response (this is known as change replays); while we make a best effort to reduce replays, they are still possible.


This is it for today. In the next post on Differential Query we will cover a deep dive on Differential Query request/response structure and how App Developers can write simple code to process Differential Query responses.

To learn more about Differential Query, please visit http://msdn.microsoft.com/en-us/library/windowsazure/jj836245.aspx

As always, we are interested to hear your feedback. You can try Differential Query now by visiting https://graphexplorer.cloudapp.net/. Click “Use Demo company”


Comments (7)

  1. SK says:

    Server returns 404 (not found) for requests with non-empty deltaLink.

    Initial query with empty deltaLink returns data, but subsequent call with deltaLink set to aad.deltaLink from initial response fails.

    At the same time it works fine for GraphDir1.OnMicrosoft.com, but not for my tenant.

  2. Hi, Could you please delete "SyncCookie.txt" file in the output folder and rerun the sample? This way it clears out the state that may have left behind when using GraphDir1.OnMicrosoft.com.

  3. Can you post the next link on Differential Query ? says:

    As mentioned by you ,You will be posting more on Differential Query and some sample code, I need a link for the same, can you please share the same?

  4. Varnikhaa says:

    How can I run my AAD graph api differential query application as multi-tenant?

    followed the same steps which is mentioned in the below GitHub sample to query graph api from my console application


    MainTenant – application is created under the MainTenant

    and I'm able to retrieve users from my console application with the below configuration

    <add key="TenantDomainName" value="MainTenant.onmicrosoft.com"/>

    <add key="AppPrincipalId" value="586d8f40-a2be-4e3c-99dc-9c31d0781920"/>

    <add key="AppPrincipalPassword" value="26ejww70hIoiVXwhLlrjta5XDvpBJ9ilvSb/kf711DQ="/>

    <add key="PullIntervalSec" value="60"/>

    But if I try to retrieve users for other tenants I get the following error

    The remote server returned an error: (401) Unauthorized.

    at System.Net.WebClient.DownloadDataInternal(Uri address, WebRequest& request)

    updated my configuration for to get users from other tenant

    <add key="TenantDomainName" value="SubTenant.onmicrosoft.com"/>

    <add key="AppPrincipalId" value="586d8f40-a2be-4e3c-99dc-9c31d0781920"/>

    <add key="AppPrincipalPassword" value="26ejww70hIoiVXwhLlrjta5XDvpBJ9ilvSb/kf711DQ="/>

    <add key="PullIntervalSec" value="60"/>

    string AuthEndpoint="https://login.windows.net/{0}";

    retrieving the accesstoken from the below method is possible

    protected string GetTokenForApplication()


               string aEndpoint = string.Format(AuthEndpoint, TenantDomainName);

               AuthenticationContext authenticationContext = new AuthenticationContext(aEndpoint, false);

               // Config for OAuth client credentials

               ClientCredential clientCred = new ClientCredential(this.AppPrincipalId, this.AppPrincipalPassword);

               AuthenticationResult authenticationResult = authenticationContext.AcquireToken(this.ProtectedResourcePrincipalId, clientCred);

               return authenticationResult.AccessToken;


    but it throws error when I execute the DownloadData method

    private byte[] DownloadData(WebClient webClient, string suffix)



                   string serviceEndPoint = string.Format(





                   return webClient.DownloadData(serviceEndPoint);


    error: The remote server returned an error: (401) Unauthorized.

    at System.Net.WebClient.DownloadDataInternal(Uri address, WebRequest& request)

    could some one pls help me to fix this error

    thanks in advance

  5. Vikram says:

    Hi Pavan,

                     I ran differential query in POSTMAN tool. with initial query set dataLink to empty. But it shows all including unchanged  users . If I run again the query it again shows all users. Initial query is :


    subsequent queries I run with dataLink= dataLink mentioned in nextLink property of previous query.

  6. Vikram says:

    Hi Pavan ,

       I got the successfull result. I have to append dataLink to last DataLink property.

      Is there any query which can gives all users and lastly it's dataLink without fetching bunch of users and nextlink for the next bunch of users?

  7. Vikram says:

    Hi Pavan,

       I got the last dataLink without needing to fetch all users.

    Now I wanted to know that is there any query which can show updated group member ship of user. because if  user is added to new group or deleted  from group that user is not appeared in "changed" users.