Teams 会議のトランスクリプトを Graph API で取得しようとして「Resource not found for the segment ‘onlineMeetings’」に詰まる――この“あるある”を、原因切り分けから正しいエンドポイント、権限、実装の落とし穴まで一気に解消します。現場運用でハマりやすいパターンを表とサンプルコードで具体化し、再現手順と検証チェックリストもまとめました。
症状
次のように呼び出すと 400 BadRequest で Resource not found for the segment ‘onlineMeetings’ が返ります。
GET https://graph.microsoft.com/v1.0/onlineMeetings/{meeting_id}/transcripts
Authorization: Bearer <access_token>
これは URL セグメントの誤りが主因です。正しいパスは /communications/onlineMeetings または /users/{userId}/onlineMeetings の配下にあります。
主な原因(要点まとめ)
| 原因 | 詳細 |
|---|---|
| エンドポイントの誤り | v1.0 では /communications/onlineMeetings/{onlineMeetingId}/transcripts または /users/{userId}/onlineMeetings/{onlineMeetingId}/transcripts を使用する。 |
| トランスクリプト未生成 | 会議中に「文字起こしを開始」していないと、終了後 API で取得できるデータは存在しない(空配列)。 |
| サポート外の会議形式 | プライベート チャネル会議や、API だけで作成した即席(アドホック)会議は現行 v1.0 の Transcript API 非対応。 |
| 権限不足 | 最低でも OnlineMeetingTranscript.Read.All が必要。アプリケーション権限の場合はアプリケーション アクセス ポリシーの付与も必須。 |
正しいエンドポイントの選び方
利用する認可方式とユースケースで URL を使い分けます。
| 用途 | 推奨エンドポイント | 備考 |
|---|---|---|
| バックエンドサービスで組織横断に取得 | GET /v1.0/communications/onlineMeetings/{onlineMeetingId}/transcripts | アプリケーション権限での利用に向く。アプリケーション アクセス ポリシーで対象ユーザーを明示する。 |
| ユーザー署名で自分の会議を取得 | GET /v1.0/users/{userId}/onlineMeetings/{onlineMeetingId}/transcripts | 委任権限での利用。{userId} は UPN も可。 |
URL 例(v1.0)
GET https://graph.microsoft.com/v1.0/communications/onlineMeetings/{onlineMeetingId}/transcripts
GET https://graph.microsoft.com/v1.0/users/{userId}/onlineMeetings/{onlineMeetingId}/transcripts
正しい onlineMeetingId を用意する
- UI の「会議 ID(数字コード)」や「会議パスコード」は Graph の
onlineMeetingIdではありません。 - 確実なのは、会議の
joinWebUrl(招待リンク)からオンライン会議リソースを取得してidを得る手順です。
# 例:ユーザー特定済みで Join URL から会議を引く(委任)
GET /v1.0/users/{userId}/onlineMeetings?$filter=JoinWebUrl eq '{joinWebUrl}'
上記の応答で得た value[0].id が {onlineMeetingId} です。これを transcripts のパスに差し込みます。
トランスクリプトが存在する条件
- 会議中に主催者または許可された参加者が「… → 文字起こしを開始」を実行していること。
- 会議ポリシーでトランスクリプト(自動文字起こし)が許可されていること。
- API から取得できるのは会議終了後のファイル(VTT/JSON メタデータ)。リアルタイム取得は不可。
満たしていない場合、API 応答は {"value": []} となります。
サポート外の会議形式と落とし穴
| 会議タイプ | トランスクリプト API | 補足 |
|---|---|---|
| 通常の予定表付き Teams 会議 | 対応 | 最も安定。主催者・出席者のポリシー要件に注意。 |
| プライベート チャネル会議 | 非対応 | チャネルのスコープ仕様により取得対象外。 |
| API だけで作成した即席(アドホック)会議 | 非対応 | create onlineMeeting で生成しただけの会議はトランスクリプト対象外。 |
必要な権限と管理者設定
最低限必要な Graph 権限
| 権限名 | 種類 | 用途 |
|---|---|---|
OnlineMeetingTranscript.Read.All | 委任 / アプリケーション | トランスクリプトの取得に必須。 |
OnlineMeetings.Read.All | 委任 / アプリケーション | オンライン会議本体を探索・照会する際に推奨。 |
アプリケーション アクセス ポリシー(アプリ権限時)
アプリケーション権限で /communications/ を使う場合、Teams 管理でアプリのアクセス範囲(対象ユーザー)を明示します。代表的な PowerShell 手順は以下の通りです。
# Teams PowerShell に接続(管理者で)
Connect-MicrosoftTeams
# アプリ登録(AppId)は Azure AD で作成済とする
$AppId = ""
# ポリシー作成(初回のみ)
New-CsApplicationAccessPolicy -Identity "GraphTranscriptPolicy" -AppIds $AppId
# アクセスさせたいユーザーに付与(個別 or グループ)
Grant-CsApplicationAccessPolicy -PolicyName "GraphTranscriptPolicy" -Identity [[email protected]](mailto:[email protected])
Grant-CsApplicationAccessPolicy -PolicyName "GraphTranscriptPolicy" -Identity [[email protected]](mailto:[email protected])
# 反映確認
Get-CsApplicationAccessPolicy -Identity "GraphTranscriptPolicy"
あわせて Azure AD 側で当該権限に対し「管理者の同意」を与えておきます。
実装スニペット(Python / Node / PowerShell)
Python(requests)
import requests
access_token = "YOUR_ACCESS_TOKEN"
meeting_id = "YOUR_MEETING_ID" # Graph の onlineMeeting.id
url = f"[https://graph.microsoft.com/v1.0/communications/onlineMeetings/{meeting_id}/transcripts](https://graph.microsoft.com/v1.0/communications/onlineMeetings/{meeting_id}/transcripts)"
headers = {"Authorization": f"Bearer {access_token}"}
resp = requests.get(url, headers=headers)
resp.raise_for_status()
for item in resp.json().get("value", []):
print("Transcript VTT URL:", item.get("contentUrl"))
print("Metadata URL:", item.get("metadataContentUrl"))
Node.js(@microsoft/microsoft-graph-client)
import { Client } from "@microsoft/microsoft-graph-client";
import "isomorphic-fetch";
const client = Client.init({
authProvider: (done) => done(null, process.env.ACCESS_TOKEN)
});
const meetingId = process.env.MEETING_ID; // onlineMeeting.id
const path = `/communications/onlineMeetings/${meetingId}/transcripts`;
const res = await client.api(`/v1.0${path}`).get();
for (const t of res.value ?? []) {
console.log("VTT:", t.contentUrl);
}
PowerShell(Microsoft Graph PowerShell SDK)
# 権限に管理者同意済みで接続
Connect-MgGraph -Scopes "OnlineMeetingTranscript.Read.All"
$meetingId = ""
$transcripts = Invoke-MgGraphRequest -Method GET -Uri "[https://graph.microsoft.com/v1.0/communications/onlineMeetings/$meetingId/transcripts](https://graph.microsoft.com/v1.0/communications/onlineMeetings/$meetingId/transcripts)"
$transcripts.value | ForEach-Object {
Write-Host "VTT:" $_.contentUrl
}
cURL(快速検証)
curl -sS -H "Authorization: Bearer <TOKEN>" \
"https://graph.microsoft.com/v1.0/communications/onlineMeetings/<onlineMeetingId>/transcripts"
レスポンス構造とダウンロード
一覧の応答は概ね次のような JSON です。
{
"value": [
{
"id": "a1b2c3...",
"createdDateTime": "2025-05-01T03:12:45Z",
"meetingId": "MSpkZWY...(省略)",
"contentUrl": "https://<短期有効なURL>/transcript.vtt",
"metadataContentUrl": "https://<短期有効なURL>/metadata.json",
"state": "transcribed"
}
]
}
contentUrlは短期有効なダウンロード URL(期限切れの場合は一覧を再取得して最新 URL を入手)。metadataContentUrlは話者やタイムスタンプ、言語などのメタ情報。
失敗しないためのポイント
URL をハードコードしない
ベースは https://graph.microsoft.com/v1.0、サービスパスは /communications/onlineMeetings/{id}/transcripts か /users/{userId}/onlineMeetings/{id}/transcripts。バージョンやセグメントは定数化し、誤入力を防ぎます。
会議の同定を厳密に
- 招待リンク(Join URL)から会議を検索して
idを取得するルートを一本化。 - 日時や件名でのあいまい検索は避ける(重複やタイムゾーン差異で誤同定しやすい)。
ポリシーの事前整備
- 主催者の会議ポリシーで「トランスクリプト許可」を有効に。
- アプリ利用の場合はアプリケーション アクセス ポリシーを事前配布(新入社員の入社日に自動付与する運用が安定)。
よくあるエラーと対処
| HTTP | メッセージ例 | 原因 | 対処 |
|---|---|---|---|
| 400 | Resource not found for the segment ‘onlineMeetings’ | URL セグメント誤り(/communications or /users/{userId} がない) | 正しいパスに修正。 |
| 401 | InvalidAuthenticationToken | トークン失効/スコープ不足 | トークン更新、必要スコープを付与。 |
| 403 | AccessDenied | 権限不足、またはアプリケーション アクセス ポリシー未付与 | 権限の管理者同意とポリシー付与を確認。 |
| 404 | ItemNotFound | 会議が見つからない、またはトランスクリプト未生成 | Join URL から会議 ID を再取得/会議中に文字起こしを開始する。 |
| 409 | Conflict | 会議リソースの状態競合 | 数秒待ってリトライ(指数バックオフ)。 |
| 429 | Too Many Requests | スロットリング | Retry-After を尊重しバックオフ。 |
| 5xx | ServiceUnavailable など | 一時的障害 | 冪等な再試行、サーキットブレーカ設計。 |
検証チェックリスト(現場向け)
- テナントでトランスクリプト許可ポリシーが有効か。
- 対象会議で実際に「文字起こしを開始」したか(テスト会議で再現)。
- Join URL から
onlineMeeting.idを取得できたか。 - アプリの権限(
OnlineMeetingTranscript.Read.All)に管理者同意済みか。 - アプリケーション アクセス ポリシーで対象ユーザーにアプリを付与済みか。
- 正しいエンドポイント(
/communicationsor/users/{userId})を使っているか。 - 応答の
contentUrlが期限切れなら再取得しているか。
VTT を SRT に変換する(Python 例)
取得した contentUrl から VTT をダウンロードしたら、字幕用途に SRT へ変換できます。
from datetime import datetime
def vtt_time_to_srt(t):
# 00:00:01.234 --> 00:00:01,234
return t.replace('.', ',')
def vtt_to_srt(vtt_text):
lines = [l.rstrip() for l in vtt_text.splitlines()]
out, idx = [], 1
buf = []
for line in lines:
if line.startswith("WEBVTT") or line.startswith("Kind:") or line.startswith("Language:"):
continue
if not line:
if buf:
out.append(str(idx))
out.extend(buf)
out.append("")
idx += 1
buf = []
continue
if "-->" in line:
start, end = [p.strip() for p in line.split("-->")]
buf.append(f"{vtt_time_to_srt(start)} --> {vtt_time_to_srt(end)}")
else:
buf.append(line)
return "\n".join(out)
# 使い方:vtt_text = open("transcript.vtt", "r", encoding="utf-8").read()
# srt_text = vtt_to_srt(vtt_text)
運用に効くサンプル設計
バッチ収集(毎朝の監査用サマリ)
- 対象ユーザーの前日会議を列挙 →
onlineMeeting.idを解決 → transcripts 一覧 → VTT/JSON をストレージへ保存。 - VTT は SRT 変換後にメディア資産と一緒に格納、メタデータ JSON は検索インデックスへ投入。
- 期限付き URL を直接保管せず、常に API で再解決する。
Web アプリ(会議サマリ表示)
- ダッシュボードで会議ごとに「トランスクリプトあり/なし」を明示。
- ダウンロードはバックエンドで代理取得し、社内ストレージの一時 URL を払い出す。
- 発話者ラベルはメタデータ JSON を優先してマッピング(表示名変更に強い)。
リアルタイムが必要な場合の選択肢
Graph の Transcripts API はリアルタイム取得に非対応です。会議中の字幕や要約が必要なら、Graph Communications Calling SDK 等で音声ストリームを取得し、外部の音声認識エンジンで同時転写するアーキテクチャを採用します。録画・保存はコンプライアンス要件に従い、同意と保護(暗号化、アクセス制御)を徹底します。
トラブル対応のフロー(簡易)
| ステップ | 確認事項 | Yes | No |
|---|---|---|---|
| 1 | URL は /communications or /users/{userId}/ か | 2 へ | URL を修正 |
| 2 | onlineMeeting.id を Join URL から取得したか | 3 へ | Join URL から再解決 |
| 3 | 会議中に文字起こしを開始していたか | 4 へ | テスト会議で開始 → 再試行 |
| 4 | 権限とポリシー(管理者同意/アクセス ポリシー)は整備済みか | 5 へ | 権限とポリシーを付与 |
| 5 | contentUrl の期限切れを考慮しているか | 完了 | 一覧を再取得して最新 URL を使用 |
完全版サンプル:Join URL → transcript VTT 取得(Python)
Join URL から会議 ID を取得 → transcripts を列挙 → VTT を保存、までを一気通貫で。
import requests, sys, os, json
GRAPH = "[https://graph.microsoft.com/v1.0](https://graph.microsoft.com/v1.0)"
TOKEN = os.environ["ACCESS_TOKEN"]
USER = os.environ.get("USER_ID") # 委任なら "me" でも可
JOIN = os.environ["JOIN_WEB_URL"]
def gget(path, params=None):
r = requests.get(GRAPH+path, headers={"Authorization": f"Bearer {TOKEN}"}, params=params)
r.raise_for_status()
return r.json()
# 1) Join URL から onlineMeeting.id を取得
meetings = gget(f"/users/{USER}/onlineMeetings", params={"$filter": f"JoinWebUrl eq '{JOIN}'"})
if not meetings.get("value"):
sys.exit("meeting not found")
mid = meetings["value"][0]["id"]
# 2) transcripts を列挙
ts = gget(f"/communications/onlineMeetings/{mid}/transcripts")
if not ts.get("value"):
sys.exit("no transcript")
# 3) VTT を保存(contentUrl は短期有効)
vtt_url = ts["value"][0]["contentUrl"]
vtt = requests.get(vtt_url).text
open("transcript.vtt","w", encoding="utf-8").write(vtt)
print("saved transcript.vtt")
セキュリティとコンプライアンスの注意
- 字幕データは個人情報や機密を含み得ます。保存先は暗号化ストレージ、アクセスは最小権限で。
- 期限付き URL を公開チャネルやログに残さない(短期でも漏洩リスク)。
- 二次利用(要約・翻訳)時は情報分類に応じたマスキング・自動失効を実装。
「これだけやれば直る」最短手順
- 会議中に一度「文字起こしを開始」して終了まで進める。
- Azure AD でアプリに
OnlineMeetingTranscript.Read.Allを付与し、管理者同意。 - Teams PowerShell でアプリケーション アクセス ポリシーを作成・対象ユーザーに付与。
GET /users/{userId}/onlineMeetings?$filter=JoinWebUrl eq '{joinWebUrl}'でidを取得。GET /communications/onlineMeetings/{id}/transcriptsで一覧 →contentUrlから VTT ダウンロード。
これで「BadRequest」エラーを解消し、狙った会議のトランスクリプトを取得できます。

コメント