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:
- List of calendars, maximum 10 items returned: https://outlook.office365.com/api/v1.0/Me/Calendars?$top=10
- List of events, maximum 10 items returned: https://outlook.office365.com/api/v1.0/Me/Events?$top=10
- List of Events on a Calendar, maximum 10 items returned: https://outlook.office365.com/api/v1.0/Me/Calendars('YOUR_CAL_ID_HERE')/Events?$top=10
- List of contacts, maximum 10 items returned: https://outlook.office365.com/api/v1.0/Me/Contacts?$top=10
- List of 10 most recent contacts: https://outlook.office365.com/api/v1.0/Me/Contacts?$orderby=DateTimeCreated+desc&$top=10
- List of inbound emails, maximum 10 items returned: https://outlook.office365.com/api/v1.0/Me/Inbox/Messages?$top=10
- List of outbound emails, maximum 10 items returned: https://outlook.office365.com/api/v1.0/Me/SentItems/Messages?$top=10
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.
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:
Then you'll want to set these values in the code powering your OAuth app:
- Client ID: the client id from your Azure Active Directory App
- Client Secret: the generated secret key from your Azure Active Directory App
- Authorization URL: https://login.windows.net/common/oauth2/authorize?resource=https://outlook.office365.com/ (+ any other OAuth URL parameters)
- Token URL: https://login.windows.net/common/oauth2/token (+ any other standard OAuth URL parameters)
- Refresh URL: https://login.windows.net/common/oauth2/token?resource=https://outlook.office365.com/ (+ any other OAuth URL parameters)
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!
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 APIsAnonymous
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 removedAnonymous
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.