Microsoft TeamsプライベートチャットにGraph APIでメッセージ送信できない原因と解決策【アプリケーション権限・委任権限】

Microsoft Teams に自動で通知を送りたいと思い、Microsoft Graph API でプライベートチャットを操作すると、チャットは作れるのにメッセージ送信だけ「Message POST is allowed in application‑only context only for import purposes」という謎エラーで止まってしまうことがあります。本記事では、このエラーの正体と、運用環境で安全かつ現実的にメッセージを自動送信するための構成パターンをわかりやすく解説します。

目次

Teams プライベートチャットにメッセージを送れない問題の整理

今回の前提は次のようなケースです。

  • Microsoft Graph API を使って プライベートチャット(one-on-one / group) を操作したい
  • Azure AD(Microsoft Entra ID)のアプリ登録で アプリケーション権限 を付与
  • POST /chats でチャットの作成は成功する
  • しかし POST /chats/{chat-id}/messages で以下のエラーになる
Message POST is allowed in application‑only context only for import purposes

つまり、

  • チャットは作れるのに、メッセージだけ送れない
  • Graph Explorer では送れるのに、自分のアプリからだと送れない

という「モヤモヤする」状態になります。

なぜ「チャットは作れるのに、メッセージだけ送れない」のか

実は、チャットを作る API と、メッセージを送る API では、要求される権限の種類が違うためです。

操作API最低限の委任権限アプリケーション権限
チャット作成POST /chatsChat.Create などChat.Create(サポートあり)
チャットにメッセージ投稿POST /chats/{chat-id}/messagesChatMessage.Send など(委任)Teamwork.Migrate.All(移行専用)

チャット作成 API はアプリケーション権限 Chat.Create をサポートしているため、クライアント資格情報フロー(client credentials)だけで実行できます。一方、メッセージ送信は 通常利用では委任権限しかサポートしておらず、アプリケーション権限は「履歴移行(import)」用途に限定されている、というのが根本原因です。


Graph API の権限モデルをざっくり復習

まずは、Graph API の 2 種類の権限モデルをサクッと整理します。

種類説明トークンの特徴主な用途
委任権限
(Delegated)
「サインイン中ユーザーの代わりに」実行。ユーザーの権限の範囲内でのみ操作可能。scp(scope)クレームに権限名が入る。Web アプリ、SPA、ネイティブアプリなど、ユーザー操作を伴うシナリオ。
アプリケーション権限
(Application)
アプリ自身の身元で実行。ユーザーが誰もサインインしていなくても動く。roles クレームに権限名が入る。バックエンドサービス、バッチ、監査・同期ツールなど。

自分のトークンがどちらかを確認する方法

  1. 取得済み Access Token を JWT デコーダ などでデコードする
  2. クレームをチェック
    • scp がある → 委任権限トークン
    • roles がある → アプリケーション権限トークン
  3. エラーが出ているときに roles しかない場合、まさに「application‑only context」で呼び出している状態です

今回のエラー文言には application-only context と書かれているので、まさに アプリケーション権限のトークンでメッセージ POST を叩いたことが示唆されています。


エラーの正体:アプリケーション権限でのメッセージ POST は「移行専用」

Microsoft の公式ドキュメントでは、チャネル/チャットのメッセージ送信について、次のように記載されています。

  • API: POST /teams/{team-id}/channels/{channel-id}/messages
  • API: POST /chats/{chat-id}/messages

権限テーブルを見ると、

  • 委任(work or school):ChannelMessage.Send / ChatMessage.Send
  • アプリケーション:Teamwork.Migrate.All(注記:「アプリケーション権限は migration のみ サポート」)

さらに Microsoft Q&A でも、同じエラーメッセージに対して次のような回答がついています。

  • アプリケーション権限は 移行モードのチーム/チャネル に対するメッセージ移行のみに利用可能
  • 通常の運用チャットにメッセージを送るには 委任権限を使う必要がある
  • 1:1 チャットをアプリケーション権限だけで送ることは現在サポートされていない

つまり、エラーメッセージを直訳するとこうなります。

「メッセージの POST は、アプリケーション権限だけのコンテキストでは インポート用途のときだけ 認められます」

「インポート用途」とは、他システムから Teams にメッセージ履歴をまとめて移行するための API(Import API)を指しています。

Import(履歴移行)API のざっくり概要

Import API は、Slack や他のチャットサービスから Teams に 既存メッセージを過去日時で流し込むための仕組みです。 主な流れは次の通りです。

  1. 移行モードのチーム を作成 POST /teams { "@microsoft.graph.teamCreationMode": "migration", "[email protected]": "https://graph.microsoft.com/v1.0/teamsTemplates('standard')", "displayName": "移行用チーム", "createdDateTime": "2020-03-14T11:22:17.043Z" }
  2. 移行モードのチャネル を作成 POST /teams/{team-id}/channels { "@microsoft.graph.channelCreationMode": "migration", "displayName": "履歴チャネル", "createdDateTime": "2020-03-14T11:22:17.047Z" }
  3. Teamwork.Migrate.All アプリケーション権限で POST /teams/{team-id}/channels/{channel-id}/messages を呼び出し、 createdDateTime や from を指定して「過去のメッセージ」を注入
  4. 最後に POST /teams/{team-id}/completeMigration で移行モードを終了

このような「移行モード」のときだけ、アプリケーション権限でのメッセージ POST が許可されます。通常のチーム/チャネル/チャットに対して同じことをすると、まさに冒頭のエラーが返ってきます。

さらに重要なポイントとして、公式ドキュメントの Import API は チーム/チャネルのメッセージのみを対象としており、1:1 やグループチャットはスコープ外と明記されています。

Import API のスコープ対象
In-scopeTeams のチーム・チャネルメッセージ、メッセージの作成日時、インライン画像など
Out-of-scope1:1 / グループチャット、プライベートチャネル、リアクションなど

このため、プライベートチャットに対してアプリケーション権限でメッセージを送ることは、移行 API でもサポートされていないという点に注意が必要です。


やりたいこと別:現実的な解決策の一覧

では、「運用環境で自動送信したい」という要望に対して、どのような構成を取るのがよいのでしょうか。目的別に整理すると次のようになります。

目的おすすめ構成主な技術
ユーザー操作をトリガーに送信(ポータルから通知、ボタン押下など)Graph API + 委任権限OAuth2 / MSAL, ChatMessage.Send, Chat.ReadWrite
完全自動の通知(バッチ、監視アラートなど)Teams Bot または Incoming WebhookBot Framework, Teams ボット, Webhook(JSON POST)
過去チャネルメッセージの一括移行Graph Import API(移行モード)Teamwork.Migrate.All, @microsoft.graph.teamCreationMode など

以下でそれぞれのパターンを具体的に見ていきます。


パターン1:ユーザー操作がある Web / ネイティブアプリ(委任権限)

社内ポータルや業務 Web アプリから、ユーザー自身の操作をトリガーに Teams にメッセージを送りたい場合は、委任権限を使った Graph API 呼び出しがもっともシンプルでサポートも厚い方法です。

必要な権限(例)

  • Chat.ReadWrite(委任)
  • ChatMessage.Send(委任)
  • チャットを新規作成する場合は Chat.Create(委任)
  • 長期的な自動処理に備えるなら offline_access スコープでリフレッシュトークン取得

実装フロー(ざっくり)

  1. Azure AD(Entra ID)にアプリ登録を作成
  2. API permissions で上記の委任権限を追加し、管理者同意(必要に応じて)
  3. アプリの種類に応じて OAuth2 フローを実装
    • SPA / ネイティブアプリ:Authorization Code + PKCE
    • サーバーサイド Web アプリ:Authorization Code フロー
  4. 取得したアクセストークン(scp に ChatMessage.Send 等が入っている)を使って Graph API を呼び出す
アプリ種別推奨フローMSAL ライブラリ例
SPA(React, Vue など)Auth Code + PKCEMSAL.js
デスクトップ / モバイルAuth Code + PKCE / Device CodeMSAL.NET / MSAL for iOS/Android
サーバーサイド WebAuth CodeMSAL.NET, MSAL Java, etc.

委任コンテキストでの最小 C# コード例

Graph SDK v5 以降を利用したシンプルなサンプルです。ここでは、既存チャット ID が分かっている前提とします。

// 1. 事前に MSAL などでユーザーアクセストークンを取得済みとする
//    scope 例: "Chat.ReadWrite ChatMessage.Send offline_access"
string accessToken = await AcquireUserAccessTokenAsync();

// 2. Graph クライアントを委任コンテキストで初期化
var graphClient = new GraphServiceClient(request =>
{
    request.Headers.Authorization =
        new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
});

var chatId = "19:[email protected]"; // 既存チャットの ID

var message = new ChatMessage
{
    Body = new ItemBody
    {
        Content = "こんにちは、Graph API からのメッセージです。",
        ContentType = BodyType.Text
    }
};

// 3. メッセージ送信
await graphClient.Chats[chatId].Messages.PostAsync(message);

この方式であれば、アプリケーション権限は一切使わずにリアルタイム送信が可能です。Graph Explorer でうまくいくのも、裏側で「委任権限のトークン」が使われているためです。


パターン2:無人バッチ・バックエンド通知(Teams Bot / Webhook)

「夜間バッチの結果を Teams に通知したい」「監視システムから障害アラートを Teams に投げたい」といった 完全自動 のシナリオでは、Graph API のアプリケーション権限だけでチャットに POST することはできません。

代わりに、次のような方法が推奨されます。

パターン2-A:Teams Bot(Bot Framework)

Teams Bot を使うと、ボット自体が「アプリケーション」として Teams に参加し、1:1 / グループチャット / チャネル に対してメッセージを送信できます。特に、プロアクティブメッセージを使うことで、ユーザー操作なしにボットから通知を飛ばすことができます。

ざっくりした構成は次の通りです。

  1. Azure Bot Service でボットを登録し、Teams チャネルを有効化
  2. Teams アプリとしてボットをパッケージングし、組織の Teams にインストール
  3. 必要なら Graph API で「ユーザーごとにボットを自動インストール」しておき、chatId を取得
  4. Bot Framework SDK(C#, Node.js, Python など)でプロアクティブメッセージ送信ロジックを実装
観点Teams Bot
対応スコープ1:1 / グループチャット / チームチャネル
完全自動送信可能(プロアクティブメッセージ)
実装コスト中〜やや高い(Bot Framework, Teams アプリ マニフェスト作成など)
拡張性会話型 UI、ボタン付きカード、アダプティブカードなど柔軟

パターン2-B:Incoming Webhook(チャネル向け)

チャネルに対する一方向通知であれば、Incoming Webhook がシンプルで扱いやすい選択肢です。

  1. Teams で通知したいチャネルを開き、「コネクタ」や「ワークフロー」から Incoming Webhook を作成
  2. Webhook URL が発行されるので、バックエンドからその URL に JSON を POST POST https://<webhook-url> Content-Type: application/json { "text": "バッチ処理が正常終了しました。", "title": "夜間バッチ完了通知" }

制約として、

  • 基本的には チャネル専用(個人チャットには送れない)
  • 2025 年以降は「Workflows for Microsoft Teams」への移行が案内されているため、中長期的には別手段も検討した方がよい

といった点がありますが、「とりあえずチャネルに通知を飛ばしたい」という用途には非常に手軽です。

(補足)サービスアカウント+委任権限での半自動化

どうしても Bot を立てたくない場合、

  • ライセンスを付与した「通知専用サービスユーザー」を 1 つ作成
  • そのユーザーで一度サインインして委任トークンを取得
  • offline_access を含めてリフレッシュトークンを保存し、バックエンドが定期的にアクセストークンに交換してメッセージ送信

という「疑似アプリケーション権限」的な構成をとることも技術的には可能です。

ただし、

  • サービスユーザーにも M365 ライセンスが必要
  • 「人間ユーザーのなりすまし」として扱われるため監査・セキュリティポリシー上の検討が必須

といった注意点があるので、公式に推奨されるのはボット方式と考えておくのが無難です。


パターン3:過去データ移行(Import モード)

「旧チャットシステムの履歴を Teams にアーカイブとして残したい」など、過去のメッセージをまとめて移したい場合は、前述の Import API(移行モード)を検討します。

特徴と制約

  • アプリケーション権限 Teamwork.Migrate.All が必要
  • 移行対象は チーム/チャネルのメッセージ に限定
  • 1:1 / グループチャットはスコープ外(サードパーティー製 migration ツールが独自ノウハウで対応しているケースが多い)
  • Import 中はチーム/チャネルが「migration mode」でロックされ、ユーザーの通常操作はほとんどできない
  • completeMigration 呼び出し後は、追加のメッセージ import ができない
向いているケース向いていないケース
他チャットサービスから Teams への一括移行
テナント間移行でチャネル履歴も残したい
日常運用中のチームに単発でメッセージを自動投稿したい
1:1 プライベートチャットの履歴を完全コピーしたい

プライベートチャットの履歴移行まで求められる場合は、Graph だけでなく、専用の移行製品(Quest, AvePoint, Apps4.Pro など)の導入も検討した方が現実的です。


よくある落とし穴とその回避策

1. アプリケーション権限だけ付与している

もっとも多いのが、Azure AD アプリに アプリケーション権限だけを付与し、委任権限を追加していないパターンです。

  • チャット作成(Chat.Create)はアプリケーション権限でも動くので、「動いているように見える」
  • メッセージ送信だけが「Message POST is allowed in application-only context only for import purposes」で失敗する

この場合は、

  • Azure ポータルの「API permissions」で Delegated permissions に
    • Chat.ReadWrite
    • ChatMessage.Send
    を追加し、必要に応じて admin consent を実行
  • アプリ側で ユーザーのサインインフロー(Auth Code + PKCE など)を実装し、委任トークンで Graph を呼び出す

という構成に変える必要があります。

2. トークンの中身を確認していない

「権限は追加したはずなのにエラーになる」という場合、実際に取得しているトークンのクレームを見るのが最短ルートです。

症状トークンのクレーム考えられる原因
Message POST is allowed ...roles のみ、scp なしclient credentials で取得したアプリケーショントークンを使っている
Forbidden / 権限不足scp ありだが ChatMessage.Send が含まれていないサインイン時の要求スコープが不足している
PreconditionFailed / application-only context not supportedroles のみ委任権限が必要な API をアプリケーション権限で叩いている

JWT を一度確認するだけで、設計上の勘違いにすぐ気付けることが多いです。

3. Teams 側のポリシー・ライセンス

Graph の権限が正しくても、次の条件を満たしていないとメッセージ送信に失敗することがあります。

  • 送信元ユーザーに Teams ライセンス が割り当てられていない
  • Teams メッセージングポリシーで チャット機能そのものが禁止 されている
  • 組織レベルで外部アプリからの投稿を制限している

とくに「個人アカウント(@outlook.com 等)では動かない」という落とし穴はよくあります。チャネルメッセージの投稿は 職場または学校アカウントのみサポートであり、個人アカウントは対象外です。

4. 新規チャット開始とメッセージ送信を混同している

Bot でも Graph API でも、「新規チャットを開始する権限」と「既存チャットに投稿する権限」は別物です。

  • 新規チャット開始:POST /chats(必要に応じて Chat.Create 権限)
  • 既存チャットに投稿:POST /chats/{chat-id}/messages

ボットの場合、しばしば ユーザーがボットとの 1:1 チャットを少なくとも一度開くか、管理者が Graph でボットをインストールしてチャットを作成しておく必要があります。


トラブルシューティングチェックリスト(まとめ)

最後に、実際にハマりやすいポイントをチェックリスト形式で整理します。

  1. トークン種別を確認
    • Access Token のペイロードを確認し、scp があるか? roles だけではないか?
    • roles しかなければ、委任トークンを取得し直す。
  2. 権限セットを見直す
    • チャット送信なら最低限:Chat.ReadWrite + ChatMessage.Send(委任)
    • アプリケーション権限 Teamwork.Migrate.All で解決しようとしていないか?(それは移行専用)
  3. API バージョンとエンドポイント
    • まずは /v1.0 を優先(/beta は仕様変更リスクあり)
    • URL が /users/{user-id}/chats/{chat-id}/messages など変則的になっていないか確認
  4. Teams ポリシー・ライセンス
    • 送信者に Teams ライセンスが付与されているか
    • メッセージングポリシーでチャットが許可されているか
    • (ボットの場合)ボットがチャット/チームにインストール済みか
  5. Import API と通常送信 API を混同していないか
    • @microsoft.graph.teamCreationMode / channelCreationMode を設定した「移行モード」のチーム/チャネルだけが Import の対象
    • 1:1 / グループチャットの履歴移行は公式 API ではサポート外であることを理解する
  6. Graph Explorer との挙動差
    • Graph Explorer は常に「委任権限」で動いていることを意識する
    • Explorer で動くのに自分のコードで動かない場合、多くは「トークン種別の違い」が原因

まとめ:設計を切り替えればエラーは解消できる

  • リアルタイム送信をしたいなら → 委任権限で Graph API を呼ぶ
  • 完全自動の通知をしたいなら → Teams Bot か Webhook を使う
  • アプリケーション権限でのメッセージ POST は、現状「チャネル履歴の移行専用」

今回の「チャットは作れるのにメッセージ送信だけエラーになる」問題は、Graph API の権限設計と Import API の制限を正しく理解し、構成を委任権限/ボット/Webhook のいずれかに切り替えることで解消できます。

もし運用設計として「誰の名義でメッセージを送るべきか」「どこまで自動化したいか」がまだ曖昧であれば、まずはその要件を整理し、それに最適なパターン(委任 or Bot or Import)を選ぶところから始めるのがおすすめです。

この記事を書いた人

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

コメント

コメントする

目次