Teams Export APIは、Microsoft Graphの getAllMessages を中心とした、Teamsのメッセージを一括で書き出すためのAPI群です。1対1チャット、グループチャット、会議チャット、チャネルの投稿を、利用者やチーム単位で日付範囲を指定して取得できます。2025年8月25日に従量課金の対象から外れましたが、委任アクセスでは呼べない、利用者ごとに回すと同じメッセージが重複する、といった癖は従来のままです。取得範囲と期限、権限と保護APIの確認、PythonとMSALで重複なく保存するコード、調整制限までを、2026年11月7日時点のMicrosoft Learnの記述に基づいて説明します。
まとめ:Teams Export APIはアプリ権限と日付範囲の差分取得で組む
Teams Export APIで書き出すときの骨格は3つです。アプリケーション権限の Chat.Read.All と ChannelMessage.Read.All に管理者の同意を与えること、lastModifiedDateTime の範囲で前回の続きだけを取ること、そして chatId とメッセージIDで重複を除いてから保存することです。
課金の設定は2025年8月25日から不要になりました。ただし、保護APIとしての扱いの案内ページは2026年11月7日時点で404になっており、申請の要否は公開情報だけでは決めきれません。本番の設計に入る前に、自社テナントで1件だけ取得して200が返るかを確かめてください。削除されたメッセージを21日より長く、編集前の版まで残したいなら、APIの実装より先にアイテム保持ポリシーを設定する必要があります。
Teams Export APIで書き出せるメッセージの範囲と取得期限の数値
エクスポート用のAPIは、通常のメッセージ取得APIとは別に用意されています。チャットやチャネルを1つずつ巡回する代わりに、利用者やチームを起点にまとめて取れるのが違いです。
getAllMessagesなど4本のエンドポイントと必要な最小権限
公式のTeams エクスポート API の解説ページと各リファレンスから、主なエンドポイントを整理すると次のとおりです。
| API | 起点と対象 | 最小権限 |
|---|---|---|
| getAllMessages | 利用者の全チャット | Chat.Read.All |
| getAllMessages | チームの全チャネル | ChannelMessage.Read.All |
| getAllRetainedMessages | 利用者の保持チャット | Chat.Read.All |
| getAllRetainedMessages | チームの保持チャネル | ChannelMessage.Read.All |
利用者が起点のAPIは /users/{id}/chats/、チームが起点のAPIは /teams/{id}/channels/ の後ろにAPI名を付けて呼びます。チャットのgetAllMessagesのリファレンスでは、1対1、グループ、会議のチャットがすべて対象です。チャネル側のgetAllMessagesは、チームID1つで全チャネルの投稿と返信を返します。共有チャネルの投稿も取得の対象です。「メンバーの追加」のような制御メッセージも取得でき、messageType eq 'systemEventMessage' の条件で取り出したり除いたりできます。
削除メッセージは21日・削除済みチームと利用者は30日という期限
Teams Export APIで後から取れるデータには期限があります。公式ページの記載を数値で並べると、利用者がTeamsの画面で削除したメッセージは削除から最大21日、削除されたチームとチャネルは最大30日、削除された利用者と非アクティブになった利用者のメッセージも最大30日です。削除済みのチームは、deletedTeamのgetAllMessagesという別のエンドポイントで取ります。
実務で問題になるのは退職者です。アカウントを削除してから30日を過ぎると、その人が参加していたチャットはエクスポートで取れなくなります。退職処理の手順書に「アカウント削除の前に書き出しを完了する」を1行入れておくだけで、取りこぼしのほとんどは防げます。
編集前の版と21日を過ぎた削除メッセージはアイテム保持ポリシーが前提
編集される前の文面や、21日を過ぎた削除メッセージを取りたい場合は、getAllRetainedMessages を使います。チャットのgetAllRetainedMessagesとチャネルのgetAllRetainedMessagesは、どちらもTeamsのアイテム保持ポリシーが設定済みであることを前提にしています。ポリシーが無いテナントでこのAPIだけを実装しても、期待した版は返りません。
保持ポリシーはMicrosoft Purviewで設定し、設定より前に消えたデータはさかのぼって取れません。プライベートチャネルについては、2025年に保存先が利用者のメールボックスからグループのメールボックスへ移行しており、保持の履歴は移行完了後の編集と削除からしか返らないとリファレンスに注記があります。ポリシーの設計はTeams のアイテム保持ポリシーの管理を先に読んでおくと、APIで取れる範囲と食い違いません。
2025年8月の課金撤廃後に残るアプリ権限と保護APIの確認事項
Teams Export APIについて書かれた古い記事の多くは、従量課金と利用申請の手続きを前提にしています。現行の公式ドキュメントと照らし合わせて、変わった点と変わっていない点を分けておきます。
従量課金の対象外になった経緯とmodel=A・model=Bの扱い
従量制課金APIの一覧の冒頭には、2025年8月25日からTeams APIは従量課金されず、課金の構成も要らないと書かれています。すでに課金用にAzureサブスクリプションを結び付けたアプリも、そのままで構いません。以前は、セキュリティ・コンプライアンス用途の model=A と、それ以外の model=B をクエリに付けて課金モデルを選んでいました。現在のgetAllMessagesのリファレンスでは、クエリパラメーターの欄に model は載っていません。
古いサンプルを流用する場合は、URLから model を外しておくと、後で読む人が課金の要否を誤解せずに済みます。Graph全体で費用が発生する経路と単価はMicrosoft Graph APIの料金で整理しています。
委任アクセスでは呼べないChat.Read.Allの管理者同意の手順
getAllMessagesの権限表では、委任アクセスは職場アカウントでも「サポートされていません」と明記されています。使えるのはアプリケーション権限だけで、チャットは Chat.Read.All、チャネルは ChannelMessage.Read.All が最小権限です。利用者の一覧を取るなら User.Read.All も加えます。アプリケーション権限は管理者しか同意できないので、開発者だけでは動作確認まで進めません。
トークンは、サインインする利用者がいないクライアント資格情報フローで取ります。これらの権限はテナント全員のチャットを読めるため、シークレットが漏れたときの影響はメールの全件閲覧と同じ水準です。本番では証明書による認証にし、アプリの所有者と同意の記録を情報システム部門で管理してください。アプリ権限と委任権限の違いそのものは、Microsoft Graph APIとはで図にしています。
保護APIの案内ページが404になった現状と403が返ったときの確認
エクスポートの解説ページは、機密データにアクセスするTeams APIを「保護された API」と位置づけ、ユーザーなしでアクセスする要件を満たせば呼び出せる、と書いています。かつては、保護APIの案内ページから申請フォームを出し、Microsoftの承認を待つ手順でした。
2026年11月7日に確認したところ、旧来の案内ページ(graph/teams-protected-apis)は日本語版も英語版も404で、申請フォームの短縮URLもMicrosoftのページには着かずBingの検索画面に転送されました。従量課金の一覧ページには「一部の保護されたAPIは使用前に要求を送信する必要がある」という文が残っているため、申請が完全に不要になったとまでは言い切れません。
そこで、実装に入る前に次の1行で判定します。200が返れば、そのテナントでは追加の手続きなしに進められます。403が返り、本文に「Invoked API requires Protected API access in application-only context」が含まれる場合は、保護APIの承認が無い状態です。この文言が返った事例はMicrosoft Q&Aのスレッドにも残っています。その場合は、Microsoftのサポート窓口に現在の申請経路を問い合わせてください。
# 取得したアプリ専用トークンで 1 件だけ取得し、HTTP ステータスだけを表示する
curl -s -o /dev/null -w "%{http_code}\n" \
-H "Authorization: Bearer $TOKEN" \
"https://graph.microsoft.com/v1.0/users/$USER_OBJECT_ID/chats/getAllMessages?\$top=1"
ライセンスについては、書き出す対象の利用者に有効なTeamsライセンスが割り当てられていることが条件です。公式ページは、Purviewのデータ損失防止(DLP)のサービスプランや追加ライセンスは不要と明記しています。
PythonとMSALで利用者のチャットを日付範囲で書き出す実装手順
ここでは、指定した利用者のチャットを、前日分の日付範囲で取得してJSONLファイルに追記するまでを動かします。毎日1回のバッチで回す前提の構成です。
Entraのアプリ登録でアプリケーション権限と資格情報を設定する手順
Microsoft Entra管理センターの「アプリの登録」でアプリを作り、次の順に設定します。
- 「APIのアクセス許可」で、Microsoft Graphのアプリケーションの許可として
Chat.Read.Allを追加する(チャネルも書き出すならChannelMessage.Read.Allも追加) - 管理者の同意を与える権限を持つ管理者が、同じ画面で管理者の同意を実行する
- 「証明書とシークレット」で資格情報を作る(検証はシークレット、本番は証明書)
- テナントID、クライアントID、シークレットを環境変数に置き、コードには書かない
コードは pip install msal requests で動きます。MSALはMicrosoftがGitHubで公開しているPython版で、acquire_token_for_client がクライアント資格情報フローの入口です。スコープは個別の権限名ではなく、https://graph.microsoft.com/.default を指定します。
lastModifiedDateTimeの範囲指定とnextLinkで全件を取るコード
次のコードは、引数で受け取った期間と利用者のオブジェクトIDについて、チャットのメッセージを全ページ取得します。
# teams_export.py : 利用者ごとのチャットを日付範囲で取得し、重複を除いて JSONL に追記する
import json, os, sys, time
import msal, requests
TENANT_ID = os.environ["TENANT_ID"]
CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"] # 本番は証明書に置き換える
GRAPH = "https://graph.microsoft.com/v1.0"
def get_token():
app = msal.ConfidentialClientApplication(
CLIENT_ID, authority=f"https://login.microsoftonline.com/{TENANT_ID}",
client_credential=CLIENT_SECRET)
result = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
if "access_token" not in result:
raise RuntimeError(result.get("error_description"))
return result["access_token"]
def get(url, token, params=None):
for _ in range(6):
r = requests.get(url, params=params, timeout=60,
headers={"Authorization": f"Bearer {token}"})
if r.status_code in (429, 503, 504): # 調整制限と一時障害は Retry-After だけ待つ
time.sleep(int(r.headers.get("Retry-After", "10")))
continue
if r.status_code == 403: # 権限不足か保護 API の未承認
raise RuntimeError("403: " + r.text[:300])
r.raise_for_status()
return r.json()
raise RuntimeError("再送の上限に達しました")
def export_user_chats(user_id, start, end, out_path, seen):
token = get_token()
url = f"{GRAPH}/users/{user_id}/chats/getAllMessages"
params = {"$top": "50",
"$filter": f"lastModifiedDateTime gt {start} and lastModifiedDateTime lt {end}"}
written = 0
with open(out_path, "a", encoding="utf-8") as f:
while url:
page = get(url, token, params)
for m in page.get("value", []):
key = (m.get("chatId"), m["id"], m.get("lastModifiedDateTime"))
if key in seen: # 同じチャットの別の参加者から返った重複
continue
seen.add(key)
f.write(json.dumps(m, ensure_ascii=False) + "\n")
written += 1
url = page.get("@odata.nextLink") # 次ページの URL には条件が含まれている
params = None
return written
if __name__ == "__main__":
# 例: python teams_export.py 2026-11-06T00:00:00Z 2026-11-07T00:00:00Z USER_OBJECT_ID_1 USER_OBJECT_ID_2
start, end, *users = sys.argv[1:]
seen = set()
for u in users:
print(u, export_user_chats(u, start, end, "teams_chats.jsonl", seen))
日付の条件は lastModifiedDateTime の範囲で指定します。作成日ではなく更新日で切るので、前日に編集されたメッセージも新しい版として拾えます。@odata.nextLink が返らなくなった時点で、その利用者の期間内のメッセージは取り終わりです。
複数の利用者を回すと同じメッセージが重複する問題とキーでの除き方
公式ページは、users/{id}/chats/getAllMessages が利用者の送信と受信の両方を返すため、同じチャットの参加者全員について呼ぶと重複したメッセージが出ると明記しています。5人のグループチャットなら、同じ発言が最大5回返る計算です。
上のコードは、chatId、メッセージID、更新日時の組で重複を除いています。更新日時まで鍵に含めるのは、編集後の版を別の行として残すためです。編集前の版を上書きしたい場合は、保存先をデータベースにし、chatId とメッセージIDを主キーにした上書き保存へ変えてください。メモリ上の seen は1回の実行の中でしか効かないので、日をまたいだ重複は保存先側の一意制約で止めます。
特定の人の発言だけが要るなら、取得の段階で絞る方法もあります。リファレンスの $filter の例にある from/user/id eq '{oid}' を使うと、指定した利用者が送ったメッセージだけが返り、転送量も減ります。
全社分の定期エクスポートで詰まる調整制限と応答データの3つの癖
全社の数千人分を毎日回す段階で効いてくるのは、次の3点です。
テナント内アプリごと毎秒200件の調整制限と$topの上限250
Graphのサービス固有の調整制限の「一括メッセージの取得」の表では、チャットとチャネルのgetAllMessagesとgetAllRetainedMessagesが、いずれもアプリ全体で毎秒1,000件、テナント内のアプリごとに毎秒200件です。同じページには、記載値の約83%を長い時間窓でも評価するという注記があります。1つのテナントで回すなら、定常的には毎秒160件程度に抑える設計が安全です。
1回の応答で返す件数は $top で指定し、公式は上限を250にするよう勧めています。ただし $top は最大値のヒントで、返る件数は保証されません。内部で複数のフォルダーから集めるため、指定より少ない件数に @odata.nextLink が付いて返ることがあり、公式はこれを仕様としています。件数が少ないページを見て「取り終わった」と判断するコードは、取りこぼしの原因になります。
並び順が保証されない応答と、編集で再出力されるメッセージの扱い
公式ページには、応答内のメッセージが作成日時や更新日時の順に並ぶ保証は無い、という注記もあります。返ってきた最後のメッセージの日時を「次回はここから」として保存する作りは、この仕様と合いません。次回の開始時刻は、取得した値からではなく、今回指定した期間の終わりの時刻で管理してください。
また、更新日時で範囲を切っているため、3日前に投稿されたメッセージでも昨日編集されれば昨日分として再び返ります。エクスポートAPIはリアクションの変更履歴にも対応しているので、文面が同じでも別の版が届く前提で受けてください。分析用のデータとして集計する場合は、同じメッセージIDの最新の版だけを使う、というルールを先に決めておくと数字がぶれません。
添付ファイルは本文のURLでなくattachmentsから取るのが前提
エクスポートの応答には、メッセージに付いたファイルの情報が含まれます。ただし公式ページは、本文に埋め込まれたURLには直接アクセスできない場合があるとして、attachments のコレクションから取得するよう案内しています。チャットに添付したファイルの実体は送信者のOneDrive、チャネルのファイルはSharePointにあるため、ファイルの中身まで保存するなら、別途それぞれのAPIで取得する処理と権限が必要です。
本文の body は、contentType が html か text のどちらかで返ります。全文検索や生成AIへの取り込みに使う場合は、保存時にタグを除いたテキスト列を別に持たせておく方が、後の処理は単純です。チャネルとSharePointの保存先の関係はTeamsとSharePointの関係で図解しています。
Teams Export APIを自社で組む場面と既製の製品で済ませる場面
課金が外れても、用途によっては自社で作らない方が筋のよい場面があります。
自社で組む価値がある用途:社内検索・分析基盤・業務データ連携
自社で組む価値があるのは、Teamsの会話を業務の仕組みに取り込む用途です。具体的には、社内の全文検索や生成AIの検索拡張にチャネルの投稿を入れる、問い合わせ対応のチャネルから件数と応答時間を集計する、案件チャネルのやり取りを案件管理システムの履歴に紐付ける、といった処理です。いずれも、取得した後の変換と保存先の設計が本体で、既製品では作り込めません。
この場合、権限は最小限に絞ります。特定のチャネルだけが対象なら、テナント全体を読むエクスポートではなく、親記事のTeams APIとはで説明した、インストール先のチームに権限を限るリソース固有の同意と変更通知の方が適しています。テナント全員のチャットを読む権限が本当に要るかを、最初に確かめてください。
自社実装を見送る場面:訴訟対応の証拠保全と規制業種の長期保存
反対に、自社実装を見送るべきなのは、法務や監督官庁に対して記録の完全性を説明しなければならない用途です。訴訟や社内調査での証拠保全なら、Microsoft Purviewの電子情報開示と保持ポリシーで対象者を保留にする方が、取得漏れや改ざんの疑いを持たれにくくなります。金融などの規制業種で長期の保存義務がある場合も、公式ページが紹介しているSmarshやProofpointのようなアーカイブ製品を先に比較する方が早道です。Purviewの守備範囲と課金はMicrosoft Purviewとはで整理しています。
監査用の保存と社内検索を両方やりたい場合も、保存義務の部分はPurviewや既製品に任せ、自社で作るのは検索や分析に流す部分だけに絞ります。権限設計から保存先の構成までをまとめて検討したい場合は、Microsoft 365・Copilot導入支援で、Teamsのデータ連携の部分だけを切り出して相談することもできます。
よくある質問
Teams Export APIの導入前によく聞かれる質問に、公式ドキュメントの記述に沿って答えます。
Teams Export APIは無料で使えますか?
2025年8月25日以降は、APIの呼び出しそのものに従量課金はかかりません。従量制課金APIの一覧に、Teams APIは課金されなくなり課金の構成も不要になったと明記されています。必要なのは、書き出す対象の利用者に割り当てられた有効なTeamsライセンスです。保存先のストレージとバッチを動かすサーバーの費用は別にかかります。
利用者としてサインインする委任アクセスでgetAllMessagesは呼べますか?
呼べません。チャットとチャネルのどちらのgetAllMessagesも、権限表で委任アクセスは非対応とされ、アプリケーション権限だけが使えます。利用者自身の権限で読むなら、チャットごとのメッセージ一覧のAPIを委任アクセスで使い、チャットを1つずつ巡回します。調整制限は1チャットあたり毎秒1件です。
Teams会議の録画や文字起こしもエクスポートできますか?
できます。エクスポートの解説ページでは、会議の開催者を指定して録画を取る getAllRecordings と、文字起こしを取る getAllTranscripts が紹介されています。権限はそれぞれ OnlineMeetingRecording.Read.All と OnlineMeetingTranscript.Read.All です。対象は現時点で予定されたプライベートの会議だけで、録画はMP4、文字起こしは既定でVTT形式です。30分から60分の会議の録画は平均で約350MBあるので、保存容量の見積もりに入れてください。
退職者のチャットはいつまでエクスポートで取得できますか?
公式ページの記載では、削除された利用者のメッセージは削除から最大30日、非アクティブになった利用者も最大30日まで取得できます。退職者のアカウントを削除する前に書き出しを済ませる手順は、退職処理のチェックリストに載せておくのが確実な対策です。長期に残す義務がある場合は、APIではなくPurviewの保持ポリシーと非アクティブメールボックスで保全します。
Microsoft 365 Copilotとの対話履歴も同じように書き出せますか?
書き出せます。エクスポートの解説ページは、Copilotとの対話を取る getAllEnterpriseInteractions を同じ系統のAPIとして紹介する内容です。調整制限の表では、このAPIはテナント内のアプリごとに毎秒30件で、Teamsのメッセージの200件より厳しいため、同じバッチで回すならCopilot分だけ並列数を落とします。
関連記事
- Teams APIとは:Graphでできることと権限設計、Webhook・Botとの使い分け【2026年11月】:エクスポート以外のチーム・チャネル・チャットの操作と権限の全体像
- Microsoft Graph APIの料金:無料の範囲と従量課金APIの単価・有効化手順【2026年11月】:Teams APIの課金撤廃の経緯と、まだ課金されるGraphのAPI
- Microsoft Purviewとは?3系統の守備範囲と課金・役割分担を実装目線で解説:保持ポリシーと電子情報開示でチャットを保全する側の仕組み
- サントリーの不正アクセスで約10万人分漏えい|M365のファイル大量ダウンロードを監査ログで調べる手順:Microsoft 365の監査ログで操作の痕跡を追う手順
- Fluentdで監査ログ基盤を作る|欠損させない転送設定と改ざん防止の保管【v1.19】:書き出したデータを欠損なく転送し改ざんされない形で保管する基盤