How to Integrate Outlook Calendar API Into Your App
- Authors
- Name
- Eraldo Forgoli
- Published on
Table of Contents
Integrate all calendars using a single API
Join our Unified Calendar API waitlist to get access to our Unified Calendar API, which allows you to integrate all calendar providers into your app using a single API.
In our last article on how to integrate the Google Calendar API into your application, we explained all the steps a developer needs to follow in order to integrate the Google Calendar API into their application.
In this article, we’ll explain how to integrate the Outlook Calendar API into your app, including setting up the Azure App Registration, setting up scopes, verification, integration gotchas, and real-world examples.
Prerequisites
This guide assumes that you already have a Microsoft Work or Developer account with access to Azure Active Directory.
Note that the ability to create applications outside a directory has been deprecated. In case you don’t have a directory, you should join the Microsoft 365 Developer Program or sign up for Azure.
How to use and integrate the Outlook Calendar API into your app
Step 1: Sign in to the Microsoft Azure Portal
Please visit the Microsoft Azure Portal Sign-in Page and sign in.
The Microsoft Azure Portal page.
Step 2: Register a new application
After you sign in to the Microsoft Azure Portal:
- Navigate to Azure Active Directory
- Search for “App Registrations” in the search bar
- Click “App Registrations”
- Click “New registration”
- Fill in the required fields:
- The first field is the Name of your application, which is the user-facing name for the application.
- Next up, you should select the supported account types. The selection is going to be different depending on what type of application you’re developing (either for internal use or multi-tenant). For this example,I’ll select “Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant)”, as this ensures users from any organization (including Outlook.com users) can use my app. This is ideal for SaaS applications or any multi-tenant applications.
- Enter the Redirect URI: This is Optional, as it might not be required depending on the type of application you’re building. If you’re developing a web application, you most likely need it, as this Redirect URI is the URI where Azure will send OAuth responses. Under the “Select a platform” dropdown, select Web, then fill in the Redirect URI input (for example,
https://yourapp.com/auth/callback
). For server-to-side auth flow, using a web redirect URI is appropriate. Please make sure the domain you’ve entered is reachable and under your control. - Click “Register”
- After clicking the “Register” button, Azure Active Directory will create your application, and it should take you to the next page, where you can copy the Client ID and Tenant ID. The Application (client) ID is a GUID that identifies your app. The Directory (tenant) ID is not always needed, as in most cases, for multi-tenant apps you’ll typically use the
common
endpoint instead. You can use the tenant ID for testing in your own tenant.
Make sure you save the Client ID and Tenant ID fields somewhere safe, usually they're stored as environment variables.
Step 3: Configure the API Permissions
After registering your app, it’s time to configure the calendar permissions. Their permissions are prompted to the OAuth flow, and the user can see what your application scopes are before approving and granting your application access to their calendars.
By default, your application is given the “User.Read” permission. In case all you want to do is sign in and read a user profile, you can skip this step.
To add API Permissions, follow these steps
- Click the “Manage” tab on the left sidebar.
- Click “API Permissions”.
- Click the “+ Add a permission” button.
- Find the “Microsoft Graph” card (it’s usually the first card in the right drawer that opens after clicking “Add a permission”.
- Choose between “Delegated permissions” and “Application permissions”. Delegated permissions are useful when your application needs access to the API as the signed-in user. Delegated permissions, on the other hand, can be used when your application runs as a background service without a signed-in user. For this example, I’ll use “Delegated Permissions”.
- Search for “Calendars”: This search will prompt you to all the calendar-related permissions. Please choose the permissions that enable your application to work properly. In most cases, you might want to select “Calendars.ReadWrite” and “Calendars.ReadWrite.Shared” (if you need access to the shared calendars). Note that the Calendar's scopes do not usually require admin consent by default, as they are user-delegated scopes, but bear in mind that some organizations restrict user consent. If a user from an external tenant is not allowed to consent, an administrator from that tenant must grant consent for your app (usually via an admin consent prompt or URL).
Step 4. Generate a Client Secret
We recommend that the calendar operations (writes, reads, updates, etc) are done from the server; that’s why you need to generate a client secret.
To generate a Client Secret, follow these steps:
- Click the “Certificates & secrets tab”
- Click “New client secret”
- Enter a description and an expiration date
After generating the client secret, please make sure you copy it and store it somewhere safe (usually in your .env file).
Note that you won't be able to see or copy the client secret after, so make sure you copy it before leaving the page.
Step 5. Branding and Verification
In the Branding & Properties section of the app registration, you can set a logo and information (app description, terms of service URL, etc.). This is optional, but recommended for a polished consent screen. It’s essential to set a Publisher Domain (typically your custom domain, verified in Azure AD) to prevent the app from displaying as "unverified" when users consent. For a multi-tenant app, Microsoft now requires apps to be publisher-verified for broad usage. If your app is not publisher verified, users outside your tenant might be blocked from consenting due to security policies introduced in November 2020
Step 6. Get familiar with the Outlook Graph API
After setting up the application and filling in all the information, we can start exploring the Microsoft Calendar Graph API, so we get more familiar with the specific APIs for creating, updating, and deleting events.
Step 7. Consider using a Unified Calendar API service to integrate all calendar providers using a single API
Although the Microsoft Graph API is fairly well-documented, we recommend using a Unified Calendar API product that allows you to integrate all calendar providers in a single API.
By using a Unified Calendar API, you get the benefit of implementing a single API into your application and supporting all calendar providers, regardless of their limitations or API differences.
Another benefit of using a single API for all calendars is that you don’t have to maintain multiple calendar APIs, deal with breaking changes, or edge cases that you never thought of.
OneCal Unified Calendar API
Example of an Outlook Calendar authorization flow
The flow chart below illustrates a simple Outlook Calendar OAuth flow that allows users to connect their Outlook Calendar to your app.
Please visit the Microsoft OAuth2 Flow documentation page to learn more about the Microsoft OAuth2 Flow.
Client Side (UI)
const microsoftOauthUrl = getMicrosoftOAuthUrl()
<button href="microsoftOauthUrl" rel="noopener noreferrer"> Connect Outlook Calendar </button>
export const SCOPES = [
"openid",
"email",
"profile",
"offline_access",
"Calendars.ReadWrite",
"User.Read",
];
export interface ClientState {
session: Session;
returnUrl?: string;
}
export function stateToB64(session: ClientState): string {
return encode(JSON.stringify(session));
}
export function getMicrosoftOAuthUrl(
state: ClientState,
) {
const nonce = uuid();
const TENANT_ID = process.env.NEXT_PUBLIC_MICROSOFT_TENANT_ID;
const params = new URLSearchParams({
client_id: process.env.MICROSOFT_CLIENT_ID || "",
redirect_uri: `${getHostName()}/api/connect/microsoft`,
response_type: "code id_token",
scope: SCOPES.join(" "),
prompt: "consent",
response_mode: "form_post",
state: stateToB64(state),
nonce,
});
return `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/authorize?${params}`;
}
- The “prompt” parameter can have one of four values:
login
,none
,consent
, andselect_account
. - The “response_mode” can have the values of
query
,fragment
, orform_post
. We choseform_post
because it tells Microsoft to send a POST request to our redirect URI (on the server).
API Side (Backend)
Next, let’s build the API handler, which is responsible for getting the code and scopes from the Outlook server and exchanging them for tokens.
In this example, we’re using zod
for validation.
const successSchema = z.object({
code: z.string(),
state: z.string(),
id_token: z.string(),
session_state: z.string().optional(),
});
const errorSchema = z.object({
error: z.string(),
error_description: z.string().optional(),
});
type ErrorParams = z.infer<typeof errorSchema>;
const querySchema = z.union([successSchema, errorSchema]);
function isError(query: Record<string, any>): query is ErrorParams {
return Boolean(query.error);
}
const microsoftHandler: NextApiHandler = async (req, res) => {
try {
const result = querySchema.parse(req.body);
if (isError(result)) {
const q = new URLSearchParams({
error: "ACCESS_DENIED",
provider: CalendarProvider.MICROSOFT,
});
console.error({ result });
return res.redirect(302, `/?${q}`);
}
const { session, returnUrl } = stateFromB64(result.state);
const { email } = decodeIdToken(result.id_token);
const { access_token, refresh_token, expires_in, scope } =
await exchangeCodeForTokens(result.code);
const connection = await upsertConnection(
{
email,
accessToken: access_token,
refreshToken: refresh_token,
expiresInSeconds: expires_in,
status: ConnectionStatus.ACTIVE,
provider: CalendarProvider.MICROSOFT,
scopes: scope,
},
session.user
);
const q = new URLSearchParams({
cid: connection.id,
});
if (returnUrl) q.append("returnUrl", returnUrl);
res.redirect(302, returnUrl ? returnUrl : `/calendars/microsoft?${q}`);
} catch (e: any) {
let error = JSON.stringify(e);
const querystr =
typeof req.query === "string" ? req.query : JSON.stringify(req.query);
const q = new URLSearchParams({
error,
provider: CalendarProvider.MICROSOFT,
});
return res.redirect(302, `/?${q}`);
}
};
export default microsoftHandler;
Similar to the client side, we recommend having these utility functions for exchanging code for tokens, etc.
const TENANT_ID = process.env.MICROSOFT_TENANT_ID;
export async function exchangeCodeForTokens(code: string) {
const data = new FormData();
data.append("client_id", process.env.MICROSOFT_CLIENT_ID || "");
data.append("scope", SCOPES.join(" "));
data.append("code", code);
data.append("redirect_uri", `${getHostName()}/api/connect/microsoft`);
data.append("grant_type", "authorization_code");
data.append("client_secret", process.env.MICROSOFT_CLIENT_SECRET || "");
try {
const result = await fetch(
`https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token`,
{
method: "POST",
body: data,
}
);
const json = await result.json();
if (json.error) throw json;
const parsed = responseSchema.parse(json);
return parsed;
} catch (e) {
console.error("Exchange failed");
throw e;
}
}
Outlook Calendar API Integration Gotchas
Verification can take time and be frustrating at times: Bear in mind that thousands, if not millions, of developers use Outlook on a daily basis, so Microsoft Teams handles thousands of application submissions daily. Make sure you fill out all the details when submitting for review, and take into account the verification process in your roadmap.
Make sure you only request the scopes you absolutely need: The Microsoft team is very thorough with their review, so make sure you only request what’s necessary for your application to perform the features. This will not only make it easier for your application to get approved, but will also help you when users grant access to your application. It would be confusing if the end user sees that your application is requesting all kinds of scopes that are unrelated to the actual features.
Webhooks expire, make sure you renew them: In case you’re registering webhooks to watch for calendar changes, make sure you set up a background job every few hours that renews any webhook subscriptions that expire in a couple of hours.
Rate limiting and throttling: Microsoft has very strict rate limiting and throttling rules, make sure you avoid one-by-one item fetches and implement rate limiting inside your application calls so you don’t get the famous “MailboxConcurrency” errors. The table below explains the Rate limits & throttling limits:
Scope Limit Notes Per mailbox (app ID & mailbox pair) 10,000 requests / 10 min and 4 concurrent requests. The famous “MailboxConcurrency” error. Upload 150 MB total PATCH/POST/PUT per 5 min per mailbox. Can hit you when attaching large ICS files or file attachments. Global Graph 130,000 requests / 10 s per app across all tenants. Rare, but big SaaS back-fills can trigger it. Retry etiquette On 429
or503/504
look forRetry-After
header, back off exponentiallyGraph API will keep throttling if you hammer it every second. Time-zone gotchas: In Outlook, a user can manually type their own timezone (you see where this is going), so make sure you handle this case as well.
Consent & permission hiccups: As mentioned when configuring the application scopes,
Calendars.ReadWrite
is user-delegated, but many tenants allow user consent, but some don’t. Be ready for an “admin consent required” error and surface a friendly “Ask your admin” flow.
Integrate all calendar providers into your app using the OneCal Unified Calendar API
Calendar integrations are our bread and butter. Since 2022, we’ve synchronized billions of events across all major calendar providers, and our scheduling links are used by thousands of professionals across the world.
The lessons we’ve learned from dealing with all calendar APIs have fueled the creation of the OneCal Unified Calendar API, allowing our users to avoid spending hundreds of hours on calendar-related issues and instead focus on implementing features that benefit their product and growth.
Join our Unified Calendar API waitlist to get notified as soon as we launch our product and benefit from the early pricing and discounts.