Microsoft Graph APIでOutlookイベントIDにスラッシュが含まれると取得できない原因と対策(iCalUId検索・ID変換)

Outlook の Office アドインで取得したイベントIDを保存し、後から Microsoft Graph API(GET /me/events/{id})で予定を取得しようとしたら、IDに「/」が含まれて失敗する――。本記事では原因の整理と、iCalUId検索・ID形式変換で安全に取得/更新する実践手順をまとめます。

目次

現象:イベントIDに「/(スラッシュ)」が入ると Graph のイベント取得が失敗する

Outlook の Office アドイン(Office.js)で取得した予定(イベント)のIDをそのままデータベースに保存し、後から Web アプリ側で Microsoft Graph API の GET /me/events/{event-id} を叩いて詳細を取ろうとすると、イベントIDの中に「/」が含まれているケースで呼び出しが失敗することがあります。URL のパスは「/」を区切りとして解釈するため、IDをパスに直書きした瞬間にパス構造が壊れてしまうのが直感的な原因です。

厄介なのは、一般的な URL エンコード(%2F)を試しても期待どおりに動かないことがある点です。アプリ・プロキシ・API ゲートウェイ・サーバー側のルーティング実装によっては、URL デコードのタイミングやセキュリティ上の制限により「エンコードされたスラッシュ」を受け付けないことがあり、結果として Graph へのリクエストが成立しません。

やりたいことよくある実装起きがちな問題安全な方向性
予定詳細を取得/me/events/{event-id}IDに / が混ざるとパス解釈で失敗iCalUId で検索 → id を取り直す
予定を更新/削除取得した id を使って PATCH/DELETE元のIDが Graph 用でない(EWS 形式など)と不整合ID形式を変換(convertToRestId / translateExchangeIds)
一時しのぎ/ を - に置換ID衝突や再現性の不安、将来の仕様変更耐性が弱い置換ではなく「検索」か「公式の変換」を採用

まず押さえるべきポイント:そのIDは「Graph の event.id」ではない可能性がある

「Outlook のアドインで取ったID」と「Graph が /me/events/{id} で期待しているID」は、同じに見えて別物であることがあります。特に Outlook アドインが返す itemId は、環境や API の使い方により EWS 形式のIDであることがあり、この形式は URL にそのまま埋め込むことを前提にしていません。

IDの種類代表的な入手元見た目の特徴(例)URL パス安全性主な用途
EWS IDOffice アドインの itemId(既定/設定次第)長い文字列、+ や / を含むことがある低い(そのままだと事故りやすい)EWS/Exchange 系APIとの連携
REST/Graph 向け ID(restId など)Office.js の変換API、Graph の変換APIURL-safe 化された表現(環境により記号が変化)高い(URLに載せやすい)Outlook REST API / Graph での操作
Graph event.idGraph のイベント取得結果opaque(意味を推測しない)原則は高いが、取り回しを誤ると失敗する場合もGraph リソースの直接参照
iCalUIdGraph のイベントプロパティ、アドインから参照できる場合もiCalendar の UID(予定の識別子)高い(クエリ文字列内で扱う)検索・突合(「見つける」用途)

この手のトラブルで最初にやるべきことは、「保存しているIDがどの形式か」を確かめることです。形式がズレているなら、URL エンコードで頑張るのではなく、公式の変換(または検索経由)に寄せた方が、運用の再現性と将来耐性が上がります。

解決策の本命:イベントIDをパスに直書きせず、iCalUId で検索して取得する

Microsoft Learn の Q&A で提案されている現実解は、イベントを id で直接引かずに iCalUId で検索して取る方法です。これなら、URL パスの「/」問題を根本から回避できます。

基本形:iCalUId でフィルターしてイベントを取得する

Web アプリ側では、次のように iCalUId を条件にイベントを検索します(id をパスに入れないのがポイントです)。

GET https://graph.microsoft.com/v1.0/me/events?$filter=iCalUId eq '{iCalUId}'

検索結果が1件に定まるなら、そのレスポンスに含まれる id を「Graph が期待する形式」として扱えます。以後の取得・更新・削除は、その id を使うのが安全です。

定期予定(繰り返し)の注意:同じ iCalUId が複数イベントに紐づくことがある

ここで気をつけたいのが、定期予定(繰り返し)や例外(1回だけ変更/キャンセル)です。これらは iCalUId が同一になることがあり、iCalUId だけだと候補が複数返る可能性があります。対策としては、開始日時(start)などの追加条件で絞り込むのが実務的です。

ケースiCalUId だけ検索すると追加で持っておくと強い情報おすすめの絞り込み方
単発の予定ほぼ1件開始日時、主催者、件名iCalUId で 1件取得 → その id を採用
定期予定(シリーズ)複数(シリーズ本体/インスタンス/例外)start、originalStart、seriesMasterId候補を開始日時で突合して 1件に定める
例外(1回だけ変更)複数の中に混ざる変更前の日時(originalStart)元の発生日時で合わせて例外を特定する

実装のコツ:検索結果を小さく、突合は確実に

運用を安定させるためのコツは次のとおりです。

  • $select で必要項目だけ取得(通信量と処理量を減らす)
  • 開始日時の比較はタイムゾーンを統一(UTC で保存し、UTC で比較する)
  • 複数ヒットしたら「正しい1件」を決めるルールを用意(最初の1件採用など曖昧な実装は事故の元)
GET https://graph.microsoft.com/v1.0/me/events?$filter=iCalUId eq '{iCalUId}'&$select=id,subject,start,end,iCalUId,seriesMasterId,type

この時点で得られた id は Graph が返した値なので、更新・削除に回す際も整合しやすくなります(ただし、後述のとおり「どのAPIで取ったIDか」を混ぜないことが重要です)。

更新・削除まで視野に入れるなら:iCalUId → id 取得 → id で操作、の2段階が堅い

iCalUId はあくまで「探す」ためのキーとして優秀です。一方で、Graph の操作(PATCH/DELETE)では多くの場合 /me/events/{id} のように id が必要になります。そこで現場で採用しやすいのが、次の2段階運用です。

段階目的呼び出し例DBに残すと良いもの
検索対象イベントを特定し Graph の id を取得GET /me/events?$filter=iCalUId eq '...'id(Graph由来), iCalUId, start
操作更新/削除/詳細取得GET/PATCH/DELETE /me/events/{id}id を第一キーとして利用

ポイントは、更新・削除の直前に、いったん iCalUId で “最新の id” を取り直す運用も選べることです。予定は移動・招待更新・再作成などで状態が変わるため、「昔のIDを永遠に信じる」より、「検索で現物に当てる」方が強い場面があります。

補足:アドイン側で取得したIDが EWS 形式なら、公式手段で Graph/REST 形式へ変換する

「検索で取る」方法が最も安全ですが、アーキテクチャ上どうしても “ID直参照” をしたい場面もあります。その場合は、IDを力技で置換するのではなく、公式のID変換APIを検討します。

Office.js:convertToRestId で URL-safe なIDに変換する考え方

Outlook アドインで取得できる itemId が EWS 形式で、URL 非安全文字を含み得ることは、Office アドイン向けドキュメントでも注意点として扱われています。そこで、アドイン側で convertToRestId により REST 形式へ変換し、バックエンドには “変換後ID” を渡す設計が取りやすくなります。

// 擬似コード(概念の例)
const ewsId = Office.context.mailbox.item.itemId;
// Outlook REST / Graph で扱いやすい形式に変換する発想
const restId = Office.context.mailbox.convertToRestId(
  ewsId,
  Office.MailboxEnums.RestVersion.v2_0
);
// restId をDBに保存、以後は restId をベースに突合

重要なのは「このIDは Graph の event.id そのものではないかもしれない」という点です。ですが、少なくとも URL で壊れやすい文字が混ざるリスクを下げる目的では有効です。既に EWS ID を大量に保存してしまっている場合も、同様の発想で “正しい形式へ寄せる” ことができます。

Microsoft Graph:translateExchangeIds で ID 形式を相互変換する

Graph には Exchange 系IDと REST/Graph 系IDを相互に変換するための translateExchangeIds が用意されています。バックエンドで EWS ID を受け取ってしまう構成でも、Graph に変換させることで後段の処理を安定させられます。

POST https://graph.microsoft.com/v1.0/me/translateExchangeIds
Content-Type: application/json

{
  "inputIds": [
    "{ewsId_1}",
    "{ewsId_2}"
  ],
  "sourceIdType": "ewsId",
  "targetIdType": "restId"
}

変換後に返ってきたIDを “Graph 呼び出し用のID” として扱えば、/me/events/{id} の事故(スラッシュ問題を含む)を回避しやすくなります。加えて、移行期に「古いDBには EWS ID、新しい処理は Graph」を混在させる場合も、変換を挟むことで設計をシンプルにできます。

やりたいことおすすめ手段強み注意点
「/」を含むIDでも確実に対象を見つけたいiCalUId 検索URLパス問題を根絶、追加条件で安定運用しやすい繰り返し予定では複数ヒットし得る
既に EWS ID を保存してしまったtranslateExchangeIds で変換し直す既存資産を活かして段階移行できる変換API呼び出し分のコストが増える
アドインで取得したIDをバックエンドに渡すアドイン側で convertToRestIdバックエンド実装を簡素化どの形式に変換して保存するかをチームで統一

「/ を – に置換」はなぜ危険なのか(そして、どうして動いてしまうことがあるのか)

スレッドの結末として「/ を - に置換したら動いた」という話が出ることがあります。たしかに短期的には動くかもしれませんが、運用としてはおすすめしません。理由は大きく3つです。

  • ID衝突のリスク:元のIDに - が含まれる可能性があり、単純置換は可逆ではありません。別のIDが同じ文字列に化ける余地が残ります。
  • 将来の仕様変更に弱い:IDは opaque(中身の意味を想定しない)であり、文字種のルールが変わってもおかしくありません。置換ルールが永遠に正しい保証はありません。
  • 障害調査が難しくなる:置換後の値が「本物のIDではない」状態になるため、ログやサポート問い合わせで “そのIDはどの形式か” の切り分けが一気に複雑になります。

もし “置換で動く” 状況があるなら、それは多くの場合、本来使うべき形式(URL-safe なID)に近い表現へ偶然寄っているだけです。再現性のある設計にするなら、iCalUId 検索か、convertToRestId/translateExchangeIds のような公式の変換に寄せるのが堅実です。

実務で困らないためのデータ設計:DBには「id だけ」ではなく複数のキーを持たせる

予定連携は、ユーザー操作(移動/キャンセル/再作成)やシリーズ展開(繰り返し)などで状態が揺れます。DBには event.id だけを保存して終わり、にすると後から詰みやすいので、次のように “復元に使える情報” をセットで持つ設計がおすすめです。

カラム例型の例保存理由更新タイミング
graph_event_id文字列Graphでの直接操作に使う第一キーGraphから取得できた時点で更新
ical_uid文字列IDが壊れた/形式不明でも検索で復元できるアドイン取得時、またはGraph取得時
start_utc日時繰り返し予定の突合、複数ヒット時の決め手予定変更を検知したら更新
end_utc日時突合の補助、表示用同上
organizer_address文字列同名・同時間帯の取り違え防止初回保存時
series_master_id文字列繰り返しシリーズの親を辿るためGraphで取得できた時点で更新

これにより「普段は graph_event_id で一発」「壊れたら ical_uid + start_utc で復元」の二段構えが作れます。復旧ロジックを先に設計しておくと、障害時の対応が圧倒的に楽になります。

おすすめの処理フロー:取得・更新・削除を安全に回す手順

処理最初に使うキーダメだった時のフォールバック最終的に使うAPI
詳細表示(GET)graph_event_idical_uid で検索して graph_event_id を再取得GET /me/events/{id}
更新(PATCH)graph_event_idical_uid + start_utc で再特定 → id 更新PATCH /me/events/{id}
削除(DELETE)graph_event_idical_uid で検索 → 正しい1件を確定 → id で削除DELETE /me/events/{id}

このフローにすると、「スラッシュが入ってパスに載らない」「保存していたIDが実は EWS 形式だった」「繰り返しで候補が複数返る」などの問題を 段階的に吸収できます。

トラブルシューティング:よくあるハマりどころとチェック項目

症状ありがちな原因確認ポイント対処
/me/events/{id} が 4xx で失敗するID内の / がパス区切りとして解釈されたリクエストURLが途中で分断されていないかiCalUId 検索に切り替える、またはID形式を変換する
%2F にしてもダメエンコードされたスラッシュを許可しない経路があるアプリのルーティング/プロキシ設定、APIゲートウェイ「パスに載せない」設計へ
$filter=id eq '...' が通らないGraph のイベントで id はフィルター対象として想定されていないフィルター可能プロパティの一覧iCalUId を使う
iCalUId 検索で複数返る繰り返し/例外の影響type、seriesMasterId、開始日時開始日時やシリーズ情報で1件に絞り込む

まとめ:スラッシュ問題は「URLエンコード」より「検索・変換」で解くのが近道

Outlook のイベントIDにスラッシュが含まれて Graph の GET /me/events/{event-id} が失敗する問題は、URL パスの性質上、根性で回避しづらい類のトラブルです。実務的には次の優先順位で対策するのが安全です。

  • 最優先:iCalUId で検索してイベントを見つけ、Graph が返す id を使う
  • 更新/削除まで含める:「検索 → id 取得 → id で操作」の2段階運用にする
  • ID形式のズレがある:convertToRestId / translateExchangeIds など公式の変換で統一する
  • 避けたい:文字の置換でごまかす(衝突・調査難・将来耐性の面で不利)

「IDはopaqueで、中身を推測しない」が Graph 連携の鉄則です。URLパスに載らないなら、パスに載せない。形式が違うなら、公式に変換する。これだけで、同じ問題に二度悩まされる確率を大きく下げられます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次