社内議事録サービスにTeams Graph API連携機能を導入しました
この記事の目次
はじめに
私たちが開発している GIJILOG(社員向け会議議事録生成AIサービス)では、Teams の会議を録音・文字起こしし、議事録を自動生成する機能を提供しています。
この機能を実装するにあたり、Microsoft Graph API を使って Teams の会議データをアプリと連携させる必要がありました。
その開発の中で、Graph API まわりでいくつかハマりどころがありました。本記事ではその経験をもとに、認証フローの設計・クエリパラメータの制約・データ構造の特性という3つのテーマについて整理しています。
Graph API 固有の話だけでなく、OAuth 2.0 + PKCE の実装パターンや OData クエリの扱いは他サービスとの連携でも活かせる内容だと思うので、ぜひ参考にしてみてください。

採用方式
GIJILOG は Next.js をベースとした BFF(Backend For Frontend)構成で構築しています。ユーザーの Microsoft アカウントと連携し、アクセストークンをサーバー側で安全に管理するため、以下の認証方式を採用しました。
OAuth 2.0 Authorization Code Flow + PKCE
外部サービスの API を呼び出すためのアクセストークンをユーザー委任で取得します。
PKCE(Proof Key for Code Exchange)は、BFF 構成での認可コードフローにおいて
認可コード横取り攻撃を防ぐために推奨されているセキュリティ拡張です。
認証フロー

BFF 側エンドポイントの役割
| エンドポイント | 役割 |
|---|---|
GET /api/{service}/authorize | PKCE パラメータ生成 → 認証 URL を返す |
GET /api/{service}/callback | state 検証 → トークン交換 → Cookie 保存 → リダイレクト |
GET /api/{service}/session | Cookie の有無で認証状態(authenticated: boolean)を返す |
プロバイダ管理画面での事前設定
callback エンドポイントの URL は、プロバイダの管理画面にリダイレクト URI として事前登録しておく必要があります。
未登録の URI へのリダイレクトはプロバイダ側で拒否されてしまうので注意してください。
Microsoft Graph API の場合は Azure Portal のアプリ登録画面(認証 > リダイレクト URI)で設定できます。
環境(開発・ステージング・本番)ごとに異なる URL を使う場合は、すべての環境の URI を登録しておきましょう。
Graph API クエリパラメータ
Graph API は OData クエリオプションをサポートしていますが、エンドポイントごとにサポートされるパラメータが異なります。
未サポートのパラメータを使うとエラーになるため、各エンドポイントの対応状況を事前に確認しておくのがおすすめです。
カレンダーイベント一覧(GET /v1.0/me/events)
| クエリパラメータ | 対応 | 内容 |
|---|---|---|
| $filter | ✔ | start/dateTime ge '...' and start/dateTime lt '...' で期間指定 |
| $select | ✔ | 取得フィールドを限定 (例: id, subject, start, end, onlineMeeting, isOnlineMeeting) |
| $orderby | ✔ | start/dateTime desc で開始日時の降順ソート |
| $top | ✔ | 取得件数の上限指定。デフォルト10件。 1週間分を取りこぼさないよう200件に設定しています |
オンライン会議一覧(GET /v1.0/me/onlineMeetings)
| クエリパラメータ | 対応 | 内容 |
|---|---|---|
$filter | ✔ | joinWebUrl eq '...' で会議 URL を指定して絞り込み |
$top | ✖ | このエンドポイントでは使用不可 |
トランスクリプト一覧(GET /v1.0/me/onlineMeetings/{id}/transcripts)
※トランスクリプト=会議の文字起こしデータ
| クエリパラメータ | 対応 | 内容 |
|---|---|---|
$filter | ✔ | createdDateTime ge ... で取得範囲を絞り込み |
$top | ✔ | デフォルト10件、上限100件。 定例会議等で同一 URL にトランスクリプトが蓄積されることを考慮して 上限値の100件に設定しています |
トランスクリプトコンテンツ
(GET /v1.0/me/onlineMeetings/{id}/transcripts/{id}/content)
| クエリパラメータ | 対応 | 内容 |
|---|---|---|
$format | ✔ | レスポンスの形式を指定。text/vtt を指定することで VTT 形式のテキストを取得できます |
データ構造と関連
Outlook のスケジュールイベント・オンライン会議・トランスクリプトは別々のリソースとして管理されており、それぞれ異なる API エンドポイントから取得します。

関連のポイント
- CalendarEvent → OnlineMeeting: 直接の外部キーは存在せず、
joinUrl(CalendarEvent)とjoinWebUrl(OnlineMeeting)の値が一致することで紐付きます - CalendarEvent と OnlineMeeting は多対1: 定例開催や会議の複製で作成されたイベントは同じ会議 URL を共有するため、複数の CalendarEvent が1つの OnlineMeeting を指すことがあります
- OnlineMeeting → Transcript は1対多: 録音の停止・再開によって複数の Transcript が生成されます
- CalendarEvent と Transcript は直接紐付いていない: Transcript の特定には、CalendarEvent のスケジュール時間と Transcript の録音時間のオーバーラップで判定する必要があります
問題:トランスクリプトの特定が困難になるケース
CalendarEvent から Transcript を直接たどれない構造上、Outlook 内で下図のような状況が起こり得ます。

具体的なOutlook側のスケジュールだとこんな感じです


← 要約データが表示される会議 要約データが表示されない会議 →
同一の会議 URL(OnlineMeeting)を使い回している場合、スケジュール時間内に複数の Transcript が存在することがあります。この場合、どの Transcript が対象かをオーバーラップの判定だけでは一意に特定できませんでした。
対応策:ユーザーによるトランスクリプト選択
前述の問題に対する根本的な解決として、現在ユーザーが手動でトランスクリプトを選択できる機能を実装中です。
具体的には、Teams 会議の参加 URL をもとに該当する OnlineMeeting に紐付くトランスクリプトの一覧を取得し、録音日時と長さをユーザーに提示します。ユーザーは一覧から連携したいトランスクリプトを選択することで、自動判定では特定できなかったケースにも対応できるようになります。
おわりに
Graph API を使った Teams 連携を実装してみて、ドキュメントだけでは見えにくい挙動がいくつかありました。クエリパラメータのエンドポイントごとの制約や、イベント・オンライン会議・トランスクリプトが疎結合な構造になっている点はその典型で、上記のような対応策も含め、実際に手を動かして初めてわかることが多いと感じました。
同様の連携を検討している方のお役に立てれば嬉しいです。
※本記事は2026年06月時点の情報です。