Microsoft Graph API で Teams 会議(onlineMeeting)を作成するとき、「主催者(Organizer)を出席者(Attendees)にも入れておきたいのに、レスポンスから消える」という疑問は非常に多く寄せられます。本記事では、その挙動が“仕様”である理由と背景、現場で取るべき実装方針、代替アーキテクチャ、コード例、テスト観点までを一気通貫で解説します。
Microsoft Graph API で onlineMeeting 作成時に「Organizer を attendees にも含めたい」問題の本質
Microsoft Graph API(v1.0)で onlineMeeting を作成する際、リクエストの participants.attendees 配列に主催者と同一のユーザー ID を入れても、レスポンスではそのユーザーが attendees から取り除かれます。この挙動はバグではなく、仕様どおりです。
- 結論:
participantsはorganizer(1 名固定)とattendees(それ以外)を厳密に分離するデータモデルで、重複は許容されません。 - 影響:Organizer を Attendees として登録しようとしても、Graph サービス側で自動的に除外されます。
- 回避策:API の設定やクエリで Organizer を attendees に強制的に残す手段は公開されていません。必要に応じてクライアント側で統合(マージ)するのが実務上の正解です。
データモデルの理解:participants の構造と分離の理由
onlineMeeting の participants は、次の 2 つの役割で構成されます。
| プロパティ | 意味 | 一意性 | 典型的な利用 |
|---|---|---|---|
organizer | 会議を作成したユーザー(主催者)。常に 1 名。 | 同一会議内で 1 名に限定(attendees と重複不可)。 | 会議の所有・開催責任、既定の権限、管理操作の主体。 |
attendees | 主催者以外の参加者の配列。 | Organizer を含めない。重複は Graph により整理。 | 招待・通知、出席管理、ロール(発表者/参加者)制御。 |
この分離により、クライアントは「会議の所有者(Organizer)」と「招待された参加者(Attendees)」を明確に判別でき、権限や UI の分岐(例:開催者のみが操作できるボタン)を設計しやすくなります。よって、Organizer を attendees に重複登録させないのは一貫した設計判断と言えます。
Outlook の event リソースとの違い
似たドメインである event(Outlook 予定表)では、実装やツールによっては Organizer が出席者としても扱われるケースがあります。一方、onlineMeeting は Teams 会議専用のデータモデルとして、Organizer と attendees が厳格に分離される点が大きな違いです。
| 観点 | onlineMeeting | event |
|---|---|---|
| 主催者の扱い | Organizer は常に 1 名で、attendees に重複不可 | 実装やクライアントによって Organizer が attendees に現れることもある |
| Teams 機能との結びつき | 会議権限、ロビー、発表者/参加者ロールなどが密結合 | 主に予定表・通知・ICS 互換性に強い |
| ユースケース適正 | リアルタイム会議、管理・制御中心 | カレンダー運用・メール通知中心 |
要件が「とにかく 1 つの一覧に主催者も含めたい」で、Teams 会議リンク自体が必須でなければ、event を使い isOnlineMeeting=true を付与する設計も検討余地があります。ただし、Teams の詳細制御や会議管理 API をフル活用するなら onlineMeeting を選ぶのが基本です。
実際の挙動を確認する:リクエストとレスポンスの例
Organizer を attendees にも入れようとしたリクエスト例
POST https://graph.microsoft.com/v1.0/me/onlineMeetings
Content-Type: application/json
{
"subject": "仕様検証ミーティング",
"startDateTime": "2025-11-01T09:00:00Z",
"endDateTime": "2025-11-01T10:00:00Z",
"participants": {
"organizer": {
"identity": { "user": { "id": "11111111-1111-1111-1111-111111111111" } }
},
"attendees": [
{ "identity": { "user": { "id": "11111111-1111-1111-1111-111111111111" } } }, // Organizer と同一(重複)
{ "identity": { "user": { "id": "22222222-2222-2222-2222-222222222222" } } }
]
}
}
レスポンス(抜粋):Organizer の重複は attendees から除外
{
"id": "a1b2c3d4e5",
"subject": "仕様検証ミーティング",
"participants": {
"organizer": {
"identity": { "user": { "id": "11111111-1111-1111-1111-111111111111" } }
},
"attendees": [
{ "identity": { "user": { "id": "22222222-2222-2222-2222-222222222222" } } }
]
}
}
ご覧のとおり、Organizer と同一 ID の要素は attendees から削除されます。これはサーバー側で「主催者は出席者に含めない」という仕様に基づき正規化されるためです。
公式に Organizer を attendees に残す方法はない:ではどう設計するか
API パラメータや追加設定で Organizer を attendees に強制的に残す方法は公開されていません。実務では次のいずれか、または複数を組み合わせます。
1. クライアント側でリストを統合する(最も手堅い)
取得した会議オブジェクトから organizer と attendees を結合し、「表示用」「集計用」など用途に応じて 1 本の配列を組み立てます。重複排除や並び順はクライアントの責務として明示します。
TypeScript 例(重複を ID で排除)
type Identity = { user?: { id?: string }, application?: any, device?: any };
type Participant = { identity: Identity };
function unifyParticipants(organizer: Participant, attendees: Participant[]): Participant[] {
const map = new Map();
const keyOf = (p: Participant) => p.identity?.user?.id ?? JSON.stringify(p.identity);
[organizer, ...attendees].forEach(p => {
const k = keyOf(p);
if (!map.has(k)) map.set(k, p);
});
return Array.from(map.values());
}
C#(.NET SDK 連携を想定)
public record IdentityUser(string? Id);
public record Identity(IdentityUser? User);
public record Participant(Identity Identity);
public static IReadOnlyList UnifyParticipants(
Participant organizer, IEnumerable attendees)
{
var dict = new Dictionary();
string KeyOf(Participant p) => p.Identity.User?.Id ?? System.Text.Json.JsonSerializer.Serialize(p.Identity);
foreach (var p in new[] { organizer }.Concat(attendees))
{
var k = KeyOf(p);
if (!dict.ContainsKey(k)) dict[k] = p;
}
return dict.Values.ToList();
}
SQL(監査・集計系での統一ビュー)
-- organizer と attendees 明細を正規化して UNION
SELECT meeting_id, user_id, 'organizer' AS role FROM meeting_organizers
UNION
SELECT meeting_id, user_id, 'attendee' AS role FROM meeting_attendees;
2. 独自の「参加者管理テーブル」を持つ
BI/レポートで Organizer を含めた統一リストが必要な場合、アプリ側のデータストアで冪等ロジックを持ち、作成・更新のたびに正規化した「参加者スナップショット」を保存します。Analytics の時点で毎回 Graph を呼ばずに済むため、性能と一貫性が向上します。
// 例:participants_snapshot (meeting_id, user_id, roles, captured_at)
// roles: 'organizer', 'attendee', 'presenter' などの配列 or ビットフラグ
3. 「1 つのコレクション」にこだわる場合は event API を検討
要件が「カレンダー通知・メール互換性が最優先」で、Teams 会議のリアルタイム制御よりも予定表運用が中心なら、event リソースで作成し、isOnlineMeeting=true を付与するアプローチもあります。
ただし、Teams 固有の権限制御やロール管理を厳密に行いたい場合は onlineMeeting を選択し、クライアント側の統合で要件を満たすべきです。
ユースケース別:Organizer を含めた一覧が欲しくなる場面と対応
| ユースケース | 課題 | 推奨アプローチ |
|---|---|---|
| 会議参加者一覧を UI に表示 | Organizer が attendees にいないと「主催者がいない」ように見える | フロントで organizer + attendees を統合して描画。Organizer はバッジ表示 |
| 出席率・稼働率の分析 | 母集団に Organizer も含めたい | ETL で統合スナップショットを作成し、ロール別に集計 |
| CSV/ICS 互換エクスポート | 他システムが単一リストを前提 | エクスポート直前に統合変換し、列に role を持たせる |
設計のコツ:衝突しない ID・キーの選び方
- 人の同一性は「ユーザー ID」を第一キーに。UPN(メール)は将来変わる可能性があるため補助キーに留める。
- Graph の identity は user / application / device など複数種を取り得るため、比較ロジックは必ず種別を考慮。人以外(ボット等)が紛れるシナリオに備える。
- 役割(role)は可変である前提で、同一ユーザーに複数ロールが付くモデルを許容する(例:Organizer かつ Presenter)。
API 実装レシピ:作成・取得・更新の基本
作成(Create)
POST https://graph.microsoft.com/v1.0/users/{organizerId}/onlineMeetings
{
"subject": "プロジェクト定例",
"startDateTime": "2025-11-05T08:00:00Z",
"endDateTime": "2025-11-05T09:00:00Z",
"participants": {
"organizer": { "identity": { "user": { "id": "{organizerId}" } } },
"attendees": [
{ "identity": { "user": { "id": "{memberA}" } } },
{ "identity": { "user": { "id": "{memberB}" } } }
]
}
}
取得(Get)
GET https://graph.microsoft.com/v1.0/me/onlineMeetings/{meetingId}?$select=id,subject,participants,startDateTime,endDateTime
更新(Patch)
参加者を入れ替える場合、Organizer を attendees に移動することはできません。Organizer を変更したい場合は「別のユーザーで新規作成」等の業務フローを検討します。
フロントエンドの UI パターン:ユーザー体験を壊さない工夫
- 参加者リストは 「主催者」セクションと「出席者」セクションを分け、画面上で視覚的に統合する(例:1 つのリストに並べつつ、Organizer には「主催者」バッジ)。
- 検索・フィルタリングはrole 別のトグルを提供し、Organizer の on/off を簡便に。
- CSV 出力時は列に
roleを含め、取り込み先で柔軟に扱えるようにする。
テスト観点(回帰を防ぐためのチェックリスト)
- Organizer と同一 ID を attendees に入れて作成 → レスポンスの attendees から除外されること。
- 参加者統合関数(unify)に Organizer と attendees を渡した場合、重複が発生しないこと。
- 異種 identity(ユーザー以外)が混在するケースで、誤マージがないこと(キー設計の妥当性)。
- 時刻(タイムゾーン/DST)・ロケールの表記ゆれに耐えるフォーマット(ISO 8601)を維持。
- 大規模会議(数百人)でも描画と検索が快適(仮想スクロール・インクリメンタル検索)。
- リトライ・冪等性:連打やネットワーク断で二重登録が起きないこと。
トラブルシュート:はまりどころと対処
- 「Organizer が attendees にいないのでおかしい」
→ 仕様どおり。UI/集計側で統合して見せる・出力する。 - 「Organizer を別の人に差し替えたい」
→ 既存会議の Organizer を入れ替える API は想定されていない運用が多い。業務設計(再作成・引き継ぎ)で対応。 - 「UPN で突合して重複扱いされない」
→ ID ベースで比較する。メール別名・変更に注意。 - 429(レート制限)
→ バックオフとバッチングで緩和。統合スナップショットを用意して取得頻度を下げる。
セキュリティとコンプライアンスのメモ
- 最小権限の原則:作成・取得・更新に必要なスコープのみを付与。
- 個人情報(表示名・メール)はログに残さない/マスキングする。
- 監査用の参加者スナップショットは暗号化し、保存期間・削除ポリシーを明文化。
実務で使える「サンプル変換」:役割付きの単一配列にまとめる
最終的に「1 本の配列」で扱いたい場合、アプリ内で次のように作ります。
// 入力(Graph の participants 相当)
const organizer = { identity: { user: { id: "111..." } }, role: "organizer" };
const attendees = [
{ identity: { user: { id: "222..." } }, role: "attendee" },
{ identity: { user: { id: "333..." } }, role: "attendee" }
];
// 出力(UI/CSV/分析で共通利用)
const unified = [
organizer,
...attendees
];
// 以降は unified を主たるデータモデルとし、表示や集計を一本化
ポイントは、「Organizer を attendees に無理やり残す」のではなく、「用途上の単一配列をアプリ側で用意」することです。API の仕様と矛盾せず、拡張にも強いアプローチです。
アンチパターン
- Organizer を attendees に入れるため、ID を一時的に書き換える(擬装する)…NG。本人同一性が崩れ、監査と整合性が壊れます。
- レスポンスを書き換えて attendees に Organizer を追加して保存する…NG。後続処理が「API の真実」と乖離し、不具合温床になります。
- UPN のみで同一判定…非推奨。将来のメール別名・ドメイン統合で破綻します。
まとめ:要点の再確認
- onlineMeeting は Organizer と Attendees が厳密に分離される仕様。Organizer を attendees に重複登録しても Graph が除外します。
- 公式に重複を許容するオプションはないため、必要なら「取得後に統合」する設計を採用します。
- 予定表中心なら
event+isOnlineMeeting=trueも検討。ただし Teams の細かな会議制御が主眼ならonlineMeetingが基本。 - 同一性はユーザー ID を第一キーに。ロールは可変で、単一配列の表示・集計はアプリ側で整えましょう。
付録:現場でそのまま使えるチェックリスト
- [仕様理解]Organizer は attendees に含めない(Graph が除外)。
- [UI 設計]表示用は organizer + attendees を統合、Organizer にバッジ。
- [データ設計]ユーザー ID を主キー、UPN は補助。ロールは多値許容。
- [分析設計]統合スナップショット(冪等)を定期作成。
- [テスト]重複除外・異種 identity 混在・大人数・429 を網羅。
- [運用]権限は最小限、PII はマスク、保存期間を明文化。

コメント