Outlook 캘린더 API를 앱에 통합하는 방법
목차
단일 API로 모든 캘린더 통합하기
단일 API를 사용하여 모든 캘린더 제공업체를 앱에 통합하려면 Unified Calendar API에 가입하세요. 신용카드는 필요하지 않습니다.
지난 Google Calendar API를 애플리케이션에 통합하는 방법에 대한 글에서, 우리는 개발자가 Google Calendar API를 자신의 애플리케이션에 통합하기 위해 따라야 하는 모든 단계를 설명했습니다.
이번 글에서는 Azure 앱 등록 설정, 스코프 설정, 검증, 통합 시 주의사항, 그리고 실제 예제까지 포함하여 Outlook Calendar API를 애플리케이션에 통합하는 방법을 설명합니다.
사전 준비 사항
이 가이드는 Microsoft Work 또는 Developer 계정이 있으며 Azure Active Directory에 접근할 수 있다는 것을 전제로 합니다.
디렉토리 외부에 애플리케이션을 생성하는 기능은 더 이상 지원되지 않으므로, 디렉토리가 없다면 Microsoft 365 Developer Program에 가입하거나 Azure에 등록해야 합니다.
Outlook Calendar API를 애플리케이션에 통합하는 방법
1단계: Microsoft Azure 포털에 로그인하기
Microsoft Azure 포털 로그인 페이지를 방문하여 로그인합니다.
Microsoft Azure 포털 페이지.

2단계: 새 애플리케이션 등록하기
Microsoft Azure 포털에 로그인한 후:
- Azure Active Directory로 이동합니다.

- 검색창에 “App Registrations” 를 검색합니다.

- “App Registrations” 를 클릭합니다.

- “New registration” 을 클릭합니다.

- 필수 필드를 입력합니다:

- 첫 번째 필드는 애플리케이션 이름으로, 사용자에게 보이는 이름입니다.
- 다음은 지원할 계정 유형을 선택하는 것입니다. 이는 애플리케이션을 내부용으로 만들지, 또는 다중 테넌트용으로 만들지에 따라 달라집니다. 이 예제에서는 “Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant)”를 선택하겠습니다. 이 옵션을 선택하면 Outlook.com 사용자 등 모든 조직의 사용자가 내 앱을 사용할 수 있습니다. 이는 SaaS 애플리케이션이나 다중 테넌트 애플리케이션에 이상적입니다.
- Redirect URI 입력: 선택 사항이며, 애플리케이션 유형에 따라 필요하지 않을 수도 있습니다. 웹 애플리케이션을 개발 중이라면 필요할 가능성이 높습니다. 이 Redirect URI는 Azure가 OAuth 응답을 보낼 URI입니다. “Select a platform” 드롭다운에서 Web을 선택하고, Redirect URI 입력란에 URI를 입력하세요 (예:
https://yourapp.com/auth/callback). 서버-사이드 인증 흐름에서는 Web 리디렉션 URI가 적합합니다. 입력한 도메인이 접근 가능하고 소유 중인지 확인하세요. - “Register” 클릭
- “Register” 버튼을 클릭하면 Azure Active Directory가 애플리케이션을 생성하며, 다음 페이지로 이동합니다. 여기서 Client ID와 Tenant ID를 복사할 수 있습니다. Application (client) ID는 애플리케이션을 식별하는 GUID입니다. Directory (tenant) ID는 항상 필요한 것은 아니며, 대부분의 다중 테넌트 앱에서는
common엔드포인트를 사용합니다. 자신의 테넌트에서 테스트할 경우 Tenant ID를 사용할 수 있습니다.
Client ID와 Tenant ID를 안전한 장소에 저장하세요. 일반적으로 환경 변수로 저장합니다.
3단계: API 권한 구성
앱을 등록한 후, 이제 캘린더 권한을 설정해야 합니다. 이 권한은 OAuth 흐름 중 사용자에게 표시되며, 사용자는 애플리케이션이 어떤 스코프를 요청하는지 확인하고 동의합니다.
기본적으로, 애플리케이션에는 “User.Read” 권한이 부여됩니다. 사용자 프로필을 읽기만 원한다면 이 단계를 건너도 됩니다.
API 권한을 추가하려면 아래 단계를 따르세요:
- 왼쪽 사이드바의 “Manage” 탭을 클릭합니다.
- “API Permissions”를 클릭합니다.

- “+ Add a permission” 버튼을 클릭합니다.

- “Microsoft Graph” 카드를 찾습니다 (일반적으로 오른쪽에 뜨는 첫 번째 카드입니다).

- “Delegated permissions”와 “Application permissions” 중 선택합니다. “Delegated permissions”는 로그인한 사용자의 권한으로 API에 접근할 때 사용합니다. “Application permissions”는 로그인한 사용자 없이 백엔드 서비스로 실행할 때 사용합니다. 이 예제에서는 “Delegated Permissions”를 사용하겠습니다.

- “Calendars”를 검색합니다: 이 검색은 캘린더 관련 권한들을 보여줍니다. 애플리케이션이 정상 작동하는 데 필요한 권한을 선택하세요. 대부분의 경우 “Calendars.ReadWrite” 와 “Calendars.ReadWrite.Shared” 를 선택하게 됩니다 (공유 캘린더 접근이 필요하다면). 캘린더 관련 스코프는 기본적으로 관리자 동의를 요구하지 않지만, 조직에 따라 사용자 동의를 제한할 수 있습니다. 외부 테넌트의 사용자가 동의할 수 없다면, 해당 테넌트의 관리자가 동의해야 합니다 (관리자 동의 프롬프트 또는 URL을 통해).

4단계: ID 토큰 활성화
모든 앱에 해당하는 사항은 아니지만, 사용자의 이름, 이메일, 프로필 사진 URL 등 정보를 얻으려면 ID Tokens 옵션을 활성화해야 합니다. 위치: Manage -> Authentication.
이는 캘린더 연결 후, 추가 API 호출 없이 사용자를 식별하기 위해 사용됩니다.

참고: OneCal Unified Calendar API를 사용하는 경우, ID Tokens 활성화는 필수입니다.
5단계: 클라이언트 시크릿 생성
캘린더 작업(쓰기, 읽기, 업데이트 등)은 서버에서 수행하는 것을 권장하므로, 클라이언트 시크릿을 생성해야 합니다.
생성 방법:
- “Certificates & secrets” 탭 클릭

- “New client secret” 클릭

- 설명과 만료 날짜 입력

클라이언트 시크릿을 생성한 후, 반드시 복사하여 안전한 장소에 저장하세요 (보통 .env 파일에 저장).
6단계: 브랜딩 및 검증
앱 등록의 Branding & Properties 섹션에서 로고와 설명(앱 설명, 약관 URL 등)을 설정할 수 있습니다. 이는 선택 사항이지만, 사용자 동의 화면을 정제되게 만들기 위해 권장됩니다. 또한, 앱이 “인증되지 않음”으로 표시되지 않도록 하기 위해 Publisher Domain(대개 Azure AD에서 인증된 맞춤 도메인)을 설정하는 것이 중요합니다. 다중 테넌트 앱의 경우, Microsoft는 퍼블리셔 검증을 필수로 요구합니다. 앱이 퍼블리셔로 인증되지 않으면, 테넌트 외부의 사용자는 보안 정책으로 인해 동의할 수 없게 됩니다 (2020년 11월부터 적용).

7단계: Outlook Graph API 익히기
앱 설정과 정보 입력을 마쳤다면, Microsoft Calendar Graph API를 살펴보며, 이벤트 생성, 수정, 삭제 등에 필요한 API에 익숙해지세요.
8단계: 단일 API로 모든 캘린더 제공자 통합 고려
Microsoft Graph API는 문서화가 잘 되어 있지만, Unified Calendar API를 사용하여 하나의 API로 모든 캘린더 제공자를 통합하는 것을 추천합니다.
Unified Calendar API를 사용하면 하나의 API만 애플리케이션에 구현해도 Google, Outlook, iCloud 등의 다양한 캘린더 제공자를 지원할 수 있습니다.
또한, 여러 캘린더 API를 개별 유지보수할 필요가 없고, 변경사항이나 예외 케이스를 처리하지 않아도 된다는 이점이 있습니다.
OneCal Unified Calendar API

Outlook Calendar 인증 플로우 예제
다음 플로우 차트는 사용자가 자신의 Outlook Calendar를 애플리케이션에 연결할 수 있도록 하는 간단한 OAuth 플로우를 보여줍니다.

Microsoft OAuth2 플로우에 대해 더 알고 싶다면 Microsoft OAuth2 Flow 문서 페이지를 방문하세요.
클라이언트 사이드(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}`;
}
API 사이드(백엔드)
다음으로는 Outlook 서버로부터 코드와 범위를 받아와 토큰으로 교환하는 역할을 하는 API 핸들러를 구축해보겠습니다.
이 예제에서는 zod를 사용하고 있습니다.
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;
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 통합 시 주의사항
- 검증 과정은 시간이 걸리고 복잡할 수 있습니다: 수많은 개발자가 Outlook을 사용하고 있으며, Microsoft Teams는 매일 수천 개의 앱 제출을 처리합니다. 제출 시 모든 정보를 빠짐없이 기입하고, 검증 과정을 제품 개발 로드맵에 포함하세요.
- 꼭 필요한 스코프만 요청하세요: Microsoft는 리뷰에 매우 철저합니다. 애플리케이션이 실제로 필요한 기능 수행에 필요한 권한만 요청하면, 승인이 더 쉬워지고 사용자 입장에서도 신뢰가 높아집니다.
- Webhook은 만료됩니다. 반드시 갱신하세요: 캘린더 변경사항을 감지하기 위해 webhook을 등록했다면, 몇 시간마다 만료되는 webhook 구독을 갱신하는 백그라운드 작업을 설정하세요.
- Rate limiting과 제한: Microsoft는 매우 엄격한 속도 제한 정책을 가지고 있으므로, 항목을 하나씩 가져오는 방식은 피하고, 내부 호출에 제한을 구현하세요. 그렇지 않으면 “MailboxConcurrency” 오류가 발생할 수 있습니다. 아래 표는 제한 사항을 설명합니다:
범위
제한
비고
메일박스 당 (앱 ID 및 메일박스 쌍)
10,000 요청 / 10분, 동시 요청 4건
“MailboxConcurrency” 오류
업로드
총 150MB PATCH/POST/PUT / 5분 / 메일박스
대형 ICS 파일 첨부 시 문제 가능성
전체 Graph API
앱당 130,000 요청 / 10초 (모든 테넌트 기준)
드물지만, 대형 SaaS 백필이 해당
재시도 지침
429,503/504발생 시Retry-After헤더 확인 및 지수 백오프 사용연속 요청 시 계속 제한됨
- 시간대 문제: Outlook에서는 사용자가 시간대를 수동으로 입력할 수 있으므로, 이 경우도 처리해야 합니다.
- 동의 및 권한 문제:
Calendars.ReadWrite는 사용자 위임 스코프이지만, 일부 테넌트는 사용자 동의를 허용하지 않을 수 있습니다. “관리자 동의 필요” 오류에 대비해, 사용자에게 “관리자에게 요청” 흐름을 안내하세요.
OneCal Unified Calendar API로 모든 캘린더 제공자를 애플리케이션에 통합하세요
캘린더 통합은 우리의 핵심 전문 분야입니다. 2022년부터 Google, Outlook, iCloud 등 주요 캘린더 제공자 간 수십억 개의 이벤트를 동기화해 왔으며, 수천 명의 전문가들이 우리 스케줄링 링크를 사용하고 있습니다.
우리가 다양한 캘린더 API에서 얻은 교훈을 바탕으로 OneCal Unified Calendar API를 개발하였으며, 사용자들이 수백 시간을 절약하고 제품과 성장을 위한 기능 개발에 집중할 수 있게 했습니다.
가입하기를 통해 OneCal Unified Calendar API를 사용해 보세요. Unified API를 사용하여 여러 캘린더 제공업체를 단일 API로 쉽게 통합할 수 있습니다.
자주 묻는 질문
Outlook Calendar API를 사용하려면 어떤 계정이 필요하나요?
Azure Active Directory에 접근할 수 있는 Microsoft Work 또는 Developer 계정이 필요합니다.
전체 캘린더 접근을 위해 어떤 권한 스코프를 추가해야 하나요?
Microsoft Graph에서 Calendars.ReadWrite (및 공유 캘린더가 필요하다면 Calendars.ReadWrite.Shared) 를 추가하세요.
퍼블리셔 도메인과 브랜딩을 설정하는 이유는?
퍼블리셔 인증과 브랜딩된 동의 화면은 앱이 “인증되지 않음”으로 표시되지 않게 하며, 대부분의 다중 테넌트 앱에 필수 조건입니다.
Outlook webhook 구독은 얼마나 자주 갱신해야 하나요?
Graph 캘린더 webhook은 몇 시간 안에 만료되므로, 만료 예정인 구독을 갱신하는 백그라운드 작업이 필요합니다.
Outlook Calendar API는 푸시 알림을 지원하나요?
예. Microsoft Graph 구독을 생성하여 변경 알림을 받을 수 있습니다. 폴링 없이 실시간 감지가 가능합니다.
Outlook, Google, iCloud 캘린더를 앱에 더 쉽게 통합할 수 있는 방법이 있나요?
예. OneCal과 같은 Unified Calendar API는 모든 주요 제공자를 하나의 일관된 JSON 인터페이스로 묶어줍니다. 이를 통해 개별 API 통합 및 유지보수가 필요 없습니다.