Zapier’s Office 365 API Journey

Since we first heard about the new Office 365 product suite, we at Zapier have been eager to dig into the APIs that would accompany it. So earlier this year, when the Office 365 APIs preview launched at https://dev.office.com/, we jumped at it!

For those of you who aren't familiar with the Office 365 APIs, the preview has these goodies:

  • A REST API for Office 365 for Business (Home & Outlook.com currently excluded)
  • OAuth2 for authentication
  • Endpoints for Mail, Calendar, Contacts, and Files/Folders
  • OData formatting (which provide tons of powerful query string operators)
  • JSON serialization

Previously, much of the Microsoft development ecosystem focused on .NET code. While .NET SDKs still have superb support, this marks an exciting time where language-agnostic REST APIs are first class citizens. For example, Zapier is mostly written in Python and we found it a breeze to connect to the new REST/OAuth/OData Office 365 APIs. Here's how we did it.

Getting started

The first thing you'll need is an Office 365 subscription. They do offer free trials, so you can sign up and start experimenting right away at https://office.microsoft.com/en-us/business/, or get a developer site at https://msdn.microsoft.com/library/office/fp179924(v=office.15).

After you get all settled and are able to log on to https://outlook.office365.com/, you're ready to explore the API. Currently, Basic Authentication is enabled (for testing purposes only) in addition to OAuth2. That means there is a really simple way to explore the API directly from your browser. Just visit https://outlook.office365.com/api/v1.0/Me and enter in your normal Office 365 login credentials; you should see some JSON containing a bunch of handy links.

Let's tour a few of those more interesting links!

A quick tour

Explore the following links to get a list of calendars and events, contacts, and email messages:

I highly recommend keeping your browser session up and running, or using a tool like https://www.hurl.it/ to experiment with requests as you go.

It is also wise to read up on the MSDN REST API docs for more information about how to use the REST API. Additionally, read the OData URI conventions doc for more information about how to use OData query strings for filtering and performance tweaks.

Some real code

Ripped straight out of Zapier, here is some real production code that shows how to use some of the useful resource URLs listed above with Basic Auth (we'll talk about OAuth in a moment!). Remember, these are the same calls you were making in your browser, just scripted!

import requests # install https://docs.python-requests.org/

EMAIL = 'you@yourdomain.onmicrosoft.com' # or your custom domain
PASSWORD = 'yourpassword' # the same one you use to log in with

# Get a list of all calendars.
calendars_url = 'https://outlook.office365.com/api/v1.0/Me/Calendars'

print('Calendars:')
response = requests.get(calendars_url, auth=(EMAIL, PASSWORD))
for calendar in response.json()['value']:
  print(calendar['Name'])
print('')

# Get a list of all events.
events_url = 'https://outlook.office365.com/api/v1.0/Me/Events'

print('Events:')
response = requests.get(events_url, auth=(EMAIL, PASSWORD))
for event in response.json()['value']:
  print(event['Subject'])
print('')

# Get a list of all contacts.
contacts_url = 'https://outlook.office365.com/api/v1.0/Me/Contacts'

print('Contacts:')
response = requests.get(contacts_url, auth=(EMAIL, PASSWORD))
for contact in response.json()['value']:
  print(contact['EmailAddress1'])
print('')

# Get a list of the 10 most recently created contacts.
contacts_url = 'https://outlook.office365.com/api/v1.0/Me/Contacts'
params = {'$orderby': 'DateTimeCreated desc', '$top': '10'}

print('10 Most Recent Contacts:')
response = requests.get(contacts_url, params=params, auth=(EMAIL, PASSWORD))
for contact in response.json()['value']:
  print(contact['EmailAddress1'])
print('')  

# Get a list of the 25 most recently received messages.
messages_url = 'https://outlook.office365.com/api/v1.0/Me/Inbox/Messages'
params = {'$orderby': 'DateTimeReceived desc', '$top': '25', '$select': 'Subject,From'}

print('25 Most Recent Messages:')
response = requests.get(messages_url, params=params, auth=(EMAIL, PASSWORD))
for message in response.json()['value']:
  print(message['Subject'])
print('')

If you are creating a prototype script, you can use Basic Auth to quickly try out the APIs, because it is really simple and easy. However, if you are performing these calls in production, or on behalf of others, don't collect their passwords, just use OAuth (which is much more pleasant for everyone). Let's cover that now.

Connecting OAuth

Now that you have the API landscape under your belt, we can upgrade from Basic Auth to OAuth2. To do that, you'll need to connect your Office 365 Subscription to an Azure account (which contains all the Active Directory apps). Just visit https://manage.windowsazure.com/ to get started.

After you have an Azure account created and connected to your Office 365 app, you'll need to create a new Active Directory application. You might need to create a new directory under your Active Directory tab, but right alongside your Active Directory users, groups, domains, and so on you should see applications. You want to create one of those.

A screenshot of the Microsoft Azure Active Directory portal.

As you create and edit your app, you'll be able to set up your logo, secret keys, redirect/reply URI, and all the other good bits you are familiar with around OAuth. The only difference is that you'll need to select your scopes at application creation time, not in the redirect URI like most apps. You can do that in the "permissions to other applications" section:

A screenshot of the Microsoft Azure permissions to other applications section of the app registration page.

Then you'll want to set these values in the code powering your OAuth app:

It is worth reiterating that you don't need to provide scopes, as those are encoded in the "permissions to other applications" section.

After you finish the flow, just use the access_token and refresh_token in the same way that most OAuth services do. You can learn more about OAuth 2.0 in Azure AD, which should cover much of the basics outlined above.

Putting it into production

Thanks to the adoption of standard web API patterns like OAuth and REST, our company Zapier was able launch its very own Office 365 Integration in just a few days using the same developer platform optimized for the most common web API patterns.

A quick checklist to run down before you go into production:

  • Make sure your refresh tokens swap for a new access token properly.
  • Use filter query strings like $filter=Price+lt+20 to reduce the number of records you request instead of requesting all records and doing it in memory.
  • Add a sanity limit query string like $top=100 to protect your performance.
  • Along with $top=100, consider using something like $orderby=DateTimeReceived+desc to get new records.
  • Use $select=DisplayName,EmailAddress1 to reduce the amount of data to only the bits you need. This is especially useful for email messages (which can be very large).

If you follow these tips, you can be sure that your API calls are quick, efficient and robust. Don't forget to keep your eyes on the Office 365 documentation as they are often updated with new endpoints and features. Likewise, the Office 365 Dev Blog is indispensable!

Bryan Helmig

This post brought to you by Bryan Helmig. Bryan is a co-founder and engineer at Zapier. In addition to being a blues/jazz guitarist and whiskey connoisseur, he's hacked on just about every API available today and has helped design and build the Zapier platform which makes 10s of millions of API calls a day.

Comments

  • Anonymous
    July 01, 2014
    Using $filter=Importance eq Microsoft.Exchange.Services.OData.Model.Importance'Low' gives me no responses back when there should be items returned. This seems to be happening for me when trying to filter using the Microsoft.Exchange.Services.OData.Model namespace. Have you come across this?

  • Anonymous
    July 02, 2014
    photohunts: I haven't seen this. If you leave off the filter do you get results with 'Low' in the Importance property?

  • Anonymous
    July 07, 2014
    Importance property actually works (my mistake), but not MeetingMessageType. I am specifically trying to filter MeetingMessageType to retrieve SeriesMaster types to reconstruct recurring meeting. The query gets a 200 response, but no values returned.

  • Anonymous
    July 07, 2014
    Hmm. Yes, I see this issue as well. Let me investigate and see what I can find out.

  • Anonymous
    July 08, 2014
    photohunts: This appears to be a filtering that hasn't been fully implemented yet. I've filed a bug with our dev team to investigate.

  • Anonymous
    August 14, 2014
    I'm seeing '429 Too Many Requests' responses during application testing. Do you know what the API throttles on and what the limits are?

  • Anonymous
    August 14, 2014
    Wyatt: I don't have any details on throttling specific to the REST APIs, but I can ask. We typically don't document specific limits because the numbers are configurable.

  • Anonymous
    August 15, 2014
    Jason: are the O365 throttles something that can be configured based on a license? I would appreciate any details that you can provide as my application depends on these APIs

  • Anonymous
    September 11, 2014
    Is there any way to filter for events? I'm trying to import events but I'd like to get only newly created events (since my last request time) - I had no success with DateTimeCreated and $orderby doesn't seem to work for events too.

  • Anonymous
    September 11, 2014
    Roland: You can filter events but currently there is no DateTimeCreated property on the Event entity. That has been added in an update that is working it's way out to the service. Once that's available I expect you to be able to filter on DateTimeCreated.

  • Anonymous
    September 11, 2014
    The comment has been removed

  • Anonymous
    September 11, 2014
    Ah yes - a third thing: is there no such query like 'give me all of the events that are relevant for this day/week/month'? Maybe I overlooked it but I'm really wondering to get all the dates I need ...

  • Anonymous
    September 12, 2014
    Roland: For now you can do a $filter=EndDate ge <now> and StartDate le <cutoff date>. However this may change before these APIs come out of preview.