code, travel & business

Six lessons learnt while using Microsoft Graph

As part of my work with Linknow the last few months I've been working extensively with Microsoft Graph, which is an API for accessing data from Azure, and other Microsoft services. Here's a list of some of the things I've learned.

Short introduction to Microsoft Graph

For those who are not aware Microsoft Graph is a REST API that exposes data and endpoints for several Microsoft services: primarily Azure and Office365 (Excel, Word, etc) from a single place.

1. Learn which resources can be found where

Probably the most productive thing I did while starting out was assembling a list of the important resources Microsoft publishes about Graph.

The thing about the Microsoft Graph API is that it is essentially just an updated version of the Azure AD Graph API which currently features deprecation notices on every doc page. However, the old versions comes with a similar set of tools where it is not as obvious that you are looking at deprecated software that will not be updated.

The old versions of the Azure AD Graph API are here:

But you probably want these updated Microsoft Graph equivalents:

App registration is managed through a whole separate system that took me way to much time to find. I have no idea why this is not simply integrated in the Azure Portal. You can find it here:

Sometimes when you are working with applications you might want to revoke consent. This can be handled from the Azure Portal, but there is also a separate page available for this purpose that you should look out for: For some reason I can revoke the consent of a application without problem using the Azure Portal, but is unable to do so from the other page, despite multiple resources online all saying the other page is the preferred way to revoke an application consent. Confusing!

2. The Graph Explorer is a great tool

The Microsoft Graph Explorer is probably the best sandbox I've ever used. You can easily test any endpoint in Microsoft Graph using your own company directory. If you don't want to sign in there is ample data and examples available in the test directory.

However, the permission UI is a bit too complicated for my taste and more examples would still be great. Alas, you get autocomplete for URLs, command history and you can effortlessly switch between the beta and v1.0 endpoint, which makes it dead simple to see how they differ in functionality. Autocomplete in the request body editor is sadly missing though.

3. Tracking changes with delta queries

An important use case whenever you are trying to keep data synchronized between two systems like we do every day at Linknow is to really nail the tracking of changes. Microsoft Graph makes this easy with delta queries although there are some things that are important to consider.

First of all there is the question of missing objects. The result of some queries can include missing queries in your result set. A good example is doing a delta query for Groups with the querystring $select=members to get a per-group sub-list of which users were added or removed from a group. A response like this might be returned:

  "@odata.context": "$metadata#groups(members)",
  "value": [
      "id": "f22f5373-0cd1-470c-b20c-41b1e2b97f67",
      "members@delta": [
          "@odata.type": "#microsoft.graph.user",
          "id": "3e09f2d4-cd0f-456c-b28c-150e1ea8fea3",
          "@removed": {
            "reason": "deleted"
          "@odata.type": "#microsoft.graph.user",
          "id": "7d9ff487-9f94-49b3-a61a-4d5fc6032199"
          "@odata.type": "",
          "id": "ba5c4787-3598-4231-9dd5-237589f08ed5"

There is two things to note. First the @removed property on the first delta user. This means that the user was removed from the group, and should be handled accordingly. Already deleted users will pop up in the result set as changes the first time you run the delta query, too.

Also note the @odata.type property of the last member. That is actually a Group! I had no idea that groups could be members of other groups in Active Directory, and this particular oversight caused the thing I was working on at the time to crash because it was only expecting users.

4. Pagination for delta queries

Dealing with delta query pagination in Microsoft Graph is different if you have previously been accustomed to using the limit/offset method, or maybe the better performing keyset method. When the result of your delta query has more pages left to return it will contain a @odata.nextLink property with an URL like this (URL omitted for brevity):

  "@odata.context": "$metadata#users",
  "@odata.nextLink": "",
  "value": [
     {"id": "..."}

You need to repeatedly follow this URL in every response until a response with a @odata.deltaLink property is returned instead.

    "@odata.context": "$metadata#users",
    "@odata.deltaLink": "",
    "value": []

The final delta link should be stored in the application and used for future calls.

An important thing to consider is that changes could have been made in the meantime when your application was following nextLinks. So a good idea when the first @odata.deltaLink is returned may be to follow that URL once to see if it has any change data, and then repeat the process if it does. If it returns an empty list then you can be certain that you have processed all the changes from the time you made the delta query until the current point in time instead of just all the changes up until the time you made the first delta query.

5. Use either the beta or the v1.0 endpoint

There is nothing stopping you from using either the beta version or the v1.0 endpoint, but you should not mix them in the same application. This might seem like obvious advice, but may be tempting anyway considering swapping is as simple as changing a small part of the path when you call the API.

Besides the new features supported by the beta (obvious difference), the default return values for some queries differ between the versions in a non-documented way. In an early version of my application I wanted to use an extra field present in the /beta/users endpoint and carelessly called it instead. When deciding to switch back to /v1.0/users because that particular field was no longer needed, my application broke because the beta no longer returned some other fields I relied on, and I had carelessly chosen not to explicitly define what I wanted from the beginning. Silly mistake that could have been easily avoided.

6. Using Open Extensions

A great way to save local data related to your application in your tenants directory is by using Open Extensions. You can save data on users, groups, and a lot of other objects quite easily.

Unfortunately you are only allowed to save two extensions per item per application (user in my case) which makes it smarter to re-use old extensions. The extensions also require a write permission to have been granted for your application on the entire resources type (i.e. for all users) which might raise questions from your end users as to why you need it.

I experienced the two-per-object limit since the ID of my extensions changed rapidly as I was developing, and after changing for the third time my application gave an error. This forced me to go back and manually delete the extensions that I created on the users that I was testing against.

Another thing to consider is that stored extensions are not sandboxed between applications, so other applications in the tenants directory with the right permissions may be able to read and modify your extensions. So don't use it for saving sensitive data!

Lastly, if you are updating existing extensions, remember to include the previous properties while you're at it (and you still want them), because an update in the eyes of Microsoft Graph is essentially a replace even if you are using the HTTP verb PATCH rather than PUT while you are doing it.

Closing remarks

Using Microsoft Graph has in the end been a good experience. I'm delighted to see Microsoft taking the time to create such a well-documented API and library. A lot of the code is even available on Github, including the documentation which you wouldn't normally expect from the Likes of Microsoft. They have been going through a lot of changes the last few years, and this is obviously a part of their effort to become a more open and transparent company, which is commendable.

While this guide can probably be seen as little else than an encouragement to read the docs carefully, I do hope that it will be of use to someone.

Have fun, and happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *

Manual Stripe invoicing for the glorious benefit of your business customers

Stripe is a great service — it enables you to easily start accepting card payments in a multitude if currencies. But Stripe lacks good support for one feature that is important for many business customers: manual invoicing. In this short article I will show you how I tackled this problem as part of my work with Linknow.

The future is now: trying out GraphQL and AWS Lambda

Lately I've been trying out some new technologies that may change web development. The GraphQL query language combined with a serverless architecture enabled by Lambda form a wonderful combination that makes the work of developing an API and managing a server obsolete. Or does it? Let's have a peek into the future!