インタビューアプリ API
WebRTC インタビュールーム機能を独立させた新基盤の公開 APIです。minedia-www のサーバーが、Session・参加トークン・録画・チャット履歴などを基盤と連携するためのサーバー間 REST API を定義します。
API 契約素案(設計成果物 F)を可視化したものです。シングルテナント想定(tenant 0 = minedia-www)。本書確定後に OpenAPI 3.1 (YAML) へ落とします。
対象スコープ
| プレーン | 通信 | 認証 | 状態 |
|---|---|---|---|
| コントロールプレーン | minedia-www サーバー → 基盤(サーバー間 REST) | API key | 本書で定義 |
| イベント通知 | 基盤 → minedia-www(Webhook) | HMAC 署名 | 本書で定義 |
ベース URL
すべてのエンドポイントは /api/v1 配下です。
https://interview.minedia.com/api/v1
エンティティ × 公開 API マトリクス
コア 16 エンティティが公開 API でどう露出するかの全体マップです。書き込み系は「調査 → 基盤」の連携点に限定し、ルーム内で発生する事実は読み取り専用 + Webhook で通知します。
| エンティティ | 公開 API での扱い | 主なエンドポイント | Webhook |
|---|---|---|---|
Session | ✅ 予約レコード(作成は Room の nested session{}・逆引き・取得・更新・キャンセル) | GETPATCHDEL /sessions ※R2-3 | — |
Room | ✅ 作成起点+取得+文字起こし構成更新+解放(Room-first) | POST /rooms、GET /rooms/{id}、PATCH /rooms/{id}、POST …/close | — |
| 参加トークン | —(テナント自己署名・発行 API なし。公開鍵を鍵レジストリへ登録し、基盤は検証のみを担う) | — | — |
Participant | ✅ 読み取り + 個別 ban | GET …/participants、POST …/ban | W1 |
EntryHistory | ✅ 読み取り(純ログ) | GET …/entry-histories | W1 |
ChatMessage | ✅ 読み取り + 削除(運用 purge)。添付は content_type=file として同居 | GET/DELETE …/chat-messages | — |
RoomEvent | ✅ 読み取り(純ログ) | GET …/room-events | — |
Recording | ✅ 読み取り + 署名 URL + 削除 | GET …/recordings、GET …/download-url | W3 recording.status_changed |
ProcessedRecording | ✅ トリム指示・手動アップロード(署名 PUT + complete)・読み取り・削除(Recording と 1:1 のシングルトン) | POST/GET/DELETE …/processed-recording、POST …/complete、GET …/download-url | W7 processed_recording.status_changed |
Transcription | ✅ 読み取り + 起動 | POST/GET …/transcriptions | W6 transcription.completed |
Handout | ✅ 登録・一覧・更新・削除 | POST/GET …/handouts | — |
ConnectionTest | ✅ 開始・履歴・結果取得 | POST/GET /connection-tests | W5 connection_test.completed |
Tenant / User | ❌ 非公開(基盤内部) | — | — |
保存列 ↔ API 項目 対応(派生・非公開・改名の早見表)
本リファレンス = 公開項目、entity-design(DB 設計) = DB カラムで別管理のため、「API にあるが DB に列が無い/DB にあるが API に出ない」が起きます。迷いやすい数件だけを橋渡しします。
種別の凡例: 保存列=列をそのまま露出/改名=列名 ≠ API 名/派生=列ではなく算出値/内包=単体 API なし・親リソースに埋め込み/非公開=基盤内部のみ。
| DB(保存実体・entity-design) | 種別 | API 公開項目 | なぜズレるか |
|---|---|---|---|
*.uuid | 改名 | id | 内部は uuid 列、API は値そのままを id として露出 |
EntryHistory.id / RoomEvent.id | 例外 | id(整数) | 高ボリュームのログ系は UUID でなく整数 id(C4 例外・カーソル内部キー兼用)。ConnectionTestEvent も整数 id だが API 非公開。ChatMessage は uuid を持つ(entity §3.8)ため対象外 |
*.external_type / external_id | 保存列 | external_type / external_id | 物理 FK なしの双方向解決キー |
ChatMessage.content_type(integer 列) | 改名/型 | content_type(text/stamp/archive_status) | DB は integer 列、API は文字列ラベルで露出(output_mode も同型) |
EntryHistory.exited_at(列名) | 改名 | left_at | DB 列は exited_at、API は left_at で露出 |
Room.livekit_session_id(生 Room SID・LiveKit 内部) | 非公開 | 生値は出さない → 派生 room.provider_inspector_url | 生 SID は基盤内部に閉じ Inspector URL に派生(旧 RtcSession を Room に統合・2026-06-29) |
RtcSession(旧テーブル・廃止) | 統合 | Room に統合。livekit_session_id/archive_* は Room 列、録画は Recording.room_id | 1:1・同一ライフサイクルゆえ独立エンティティ廃止 |
Recording.storage_key(S3 キー・内部) | 非公開 | 出さない → 派生 download-url(短期署名 URL) | 直リンクせず署名 URL を払い出し |
Transcription.result_json(内部) | 非公開 | 出さない → 派生 result_url(署名 URL) | 同上 |
Transcription.error_message(内部) | 非公開 | 出さない → Webhook W6 failure_reason | 内部エラー詳細は非公開、通知は要約した failure_reason のみ |
ConnectionTest.raw_data(engine 生統計・内部) | 非公開 | 出さない → 派生 summary / baseline / load / provider_inspector_url | エンジン中立な集計値のみ露出 |
Session.access_token | 保存列 | access_token | 移行期互換でそのまま露出(参加認可の本線は JWT) |
ChatMessage.attachment_key(S3 キー・内部) | 非公開 | (出さない)→ 派生 attachment.url(短期署名 URL) | storage_key → download-url と同型 |
Tenant / User(運用台帳)/ ConnectionTestEvent | 非公開 | —(API なし) | 基盤内部 |
読み方: 「API に provider_inspector_url があるのに DB に列が無い」=派生(保存実体は livekit_session_id)。「DB に storage_key / result_json / raw_data があるのに API に無い」=非公開(代わりに署名 URL や集計値を派生で返す)。
認証
API key はテナント(サーバー)を認証する長命・回転式の資格情報です。ブラウザには絶対に露出させないでください。
すべてのリクエストの Authorization ヘッダに API key を付与します。形式は <key_id>.<secret> です。
認証ヘッダ
PoC では tenant 0 の鍵 1 本のみ。scope はなく「有効 / 無効」の二値です。失効・未指定・不正な鍵はすべて 401 UNAUTHORIZED を返します。
| ヘッダ | 説明 |
|---|---|
| Authorization必須 | ApiKey <key_id>.<secret> |
curl https://interview.minedia.com/api/v1/sessions \ -H "Authorization: ApiKey key_live_a1b2.sk_9f8e7d…"
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "API キーが無効です"
}
}
照合方式(サーバー側の検証)
基盤は secret の平文を保存しません。secret は発行時に一度だけ提示され、以後基盤が保持するのは bcrypt ハッシュのみです(漏洩しても平文は復元不可)。テナント側は受け取った secret を AWS Secrets Manager 等で保管してください。
リクエストごとの照合は次の手順で行われます。
| # | ステップ | 内容 |
|---|---|---|
| 1 | ヘッダ分解 | ApiKey <key_id>.<secret> を key_id(非機密の公開識別子)と secret に分解します |
| 2 | キー特定 | key_id でキーを 1 件特定します(見つからなければ 401) |
| 3 | 状態確認 | 失効済み(無効化・ローテーション後の旧キー)なら 401 |
| 4 | ハッシュ照合 | 届いた secret を保存済みハッシュと同じソルト・コストで再計算し、結果を定数時間比較(timingSafeEqual)で突き合わせます。復号は行いません |
| 5 | テナント確定 | キーに紐づくテナントがリクエストの所属スコープになります(テナント分離の起点) |
key_id 不明・失効済み・secret 不一致のいずれでも、レスポンスは同一の 401 UNAUTHORIZED です。「key_id は正しかった」等のヒントを攻撃者に与えないための仕様であり、比較の定数時間化と併せて総当たり・タイミング攻撃への防御になります。
キーの回転は「新キー発行 → テナント側の切替 → 旧キー失効」の順で行い、切替期間中は新旧 2 本が並行して有効です。ダウンタイムなしで差し替えられます。
リソース識別子 / external_ref
基盤の全リソースは 単一のフィールド名 id で識別します。値の形式はリソース種別により UUID 文字列 または 整数 の 2 通りです。テナント側 PK との対応は external_type / external_id のペアで双方向解決します。
id値が UUID でもフィールド名は id とします(内部 DB カラムは uuid のまま)。関連参照も session_id / room_id のように _id 接尾辞で揃えます。
id の値形式(2 種類)
| 形式 | 対象リソース | 例 |
|---|---|---|
| ① UUID 文字列 | 通常リソース(Session / Room / Participant / Recording / Transcription / ChatMessage / Handout / ConnectionTest / User / Tenant)。ChatMessage のみクライアント採番(送信の冪等キー) | "id": "0190a1b2-…" |
| ② 整数 | 高ボリュームのログ系(EntryHistory / RoomEvent) | "id": 1001 |
① UUID 形式について
基盤採番の不透明文字列で、内部キーは UUIDv7(時刻ソート可能)。推測不能なため列挙攻撃の二次防御になり、テナント側はこの文字列をそのまま保持して逆引きに使います。
高ボリュームなログ系リソース(EntryHistory / RoomEvent。API 非公開の ConnectionTestEvent も同様)は UUID を持たず、BIGINT の整数 を id として露出します(カーソルの内部キーにも使用)。それ以外のリソースが UUID 形式という原則に対する明示的な例外です(ChatMessage は uuid を持つため UUID 形式)。
external_ref(テナント側 PK との対応)
external_type / external_id のペアで、物理 FK を張らずにテナント側リソースと双方向解決します。
| リソース | external_type | 例 |
|---|---|---|
| Session | Slot / ExtemporarySession | {"Slot","123"} / {"ExtemporarySession","45"} |
| Participant | User / Employee / Operator | {"User","999"} |
| Handout | ProjectHandout(Project 系のみ。即席の提示物は external_ref なしで直接登録) | {"ProjectHandout","12"} |
テナント分離(L0)
他テナント所有のリソースと、存在しないリソースは、どちらも 404 NOT_FOUND を返します(存在秘匿。403 は返しません)。
PoC は 1 テナントですが、突合コードは最初から実装します。これにより、将来のマルチテナント化時にアクセス制御の挙動が変わらないことを保証します。
適用範囲(Phase 4): この L0 存在秘匿は 取得・変更・削除・一覧・逆引きを含む全 resource エンドポイントに既定で適用します。ban は他テナントの participant に対しても 404(「ban しようとした」事実も秘匿)。逆引き一覧は自テナントの external_ref のみ突合(他テナントの同値 external_id は不可視)。minedia_observer(基盤運営の越境観察)は L0 の唯一の例外ロールですが、PoC(tenant 0 のみ)では越境の発火経路がなく、越境監査ログは v2(契約予約のみ)。
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "リソースが見つかりません"
}
}
レスポンス形式
成功・エラーともに success フラグで包むエンベロープ形式です(truffle-survey-v2 準拠)。
成功レスポンス
success: true と data を返します。本書のリソース表現・レスポンス例は原則として data の中身を示します(実レスポンスはエンベロープで包まれます)。
エラーレスポンス
success: false と error を返します。code は UPPER_CASE の機械可読コード、details[] はフィールド単位のエラー(details[].code も UPPER_CASE)。
/.well-known/jwks.json(RFC 7517 標準形)と /health / /health/ready(素の liveness / readiness)はエンベロープで包みません。204 No Content はボディなし。
日時フォーマット
ISO 8601 / UTC(例 2026-07-01T06:00:00Z)。
{
"success": true,
"data": { "id": "0190a1b2-…" }
}
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "入力値が不正です",
"details": [
{
"field": "ends_at",
"code": "BEFORE_STARTS_AT",
"message": "終了時刻は開始時刻より後である必要があります"
}
]
}
}
ページネーション
一覧系エンドポイントはカーソルページングです(truffle 準拠)。カーソルは採番時刻順の安定ソートを表す不透明 Base64 文字列で、クライアントは中身を解釈しません。
リクエストパラメータ
| パラメータ | 説明 |
|---|---|
| cursor任意 | 前ページレスポンスの next_cursor をそのまま渡す Base64 トークン。初回は省略 |
| limit任意 | 1〜100(default 20) |
レスポンスフィールド
| フィールド | 説明 |
|---|---|
| items[] | リソース表現の配列 |
| next_cursor | 次の cursor に渡す不透明 Base64。終端では null |
| has_more | 次ページの有無(boolean) |
内部キーは UUID リソースでは UUIDv7(時刻ソート可能)、ログ系では整数 id を用います。各一覧の既定ソート順は各エンティティの節に明記します。
{
"success": true,
"data": {
"items": [ { "id": "0190a1b2-…" } ],
"next_cursor": "eyJrIjoxMDQyfQ",
"has_more": true
}
}
curl "…/api/v1/sessions?cursor=eyJrIjoxMDQyfQ&limit=20" \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
ステータスコード規約
400(スキーマ違反)と 422(業務ルール違反)を分離します。真の競合は 409 を維持します。
| HTTP | 用途 |
|---|---|
| 200 / 201 / 204 | 取得 / 作成 / 削除・失効(冪等) |
| 400 | スキーマ / パース違反(型・必須・enum・単一フィールドの範囲・ペア指定 / JSON parse 失敗)。INVALID_REQUEST / INVALID_JSON |
| 401 | API key 不正(UNAUTHORIZED) |
| 404 | 不存在 or 他テナント(L0 存在秘匿・NOT_FOUND) |
| 409 | 真の状態競合(解放済み・入室実績あり・ban・external_ref 重複など) |
| 413 | ファイルサイズ超過(FILE_TOO_LARGE) |
| 422 | スキーマ通過後の業務ルール違反(時間窓・録画未完・拡張子など。VALIDATION_ERROR 系) |
| 429 | レート制限(RATE_LIMIT_EXCEEDED・PoC では未実装・予約) |
型・必須・enum・単一フィールドの数値範囲・ペア指定など スキーマで弾けるもの = 400。スキーマは通るが時間窓・状態・録画進捗などの 意味的 / 横断的ルール違反 = 422。409 は「既に存在 / 既にその状態」という競合に限定します。
冪等性(Idempotency-Key)
ネットワーク再送による二重作成を防ぐため、すべての作成系 POST は Idempotency-Key ヘッダを標準採用します(Stripe 型)。
挙動
同一キーの再送は初回レスポンス(ステータス + ボディ)をそのまま返します(処理は 1 回だけ)。キーは Redis に保存(TTL = 24h 仮値)。
| ケース | 挙動 |
|---|---|
| 同一キー + 同一ボディ | 初回レスポンスを再生 |
| 同一キー + 異なるボディ | 422 IDEMPOTENCY_KEY_CONFLICT |
対象エンドポイント
すべての作成系 POST(/sessions、/connection-tests、/handouts、/transcriptions、/processed-recording、ban)。external_ref 一意などの自然キーは二次防御として併存します。
PoC では未指定リクエストも受理します(移行期互換)。将来必須化の余地あり。
curl -X POST …/api/v1/sessions \ -H "Authorization: ApiKey key_live_a1b2.sk_…" \ -H "Idempotency-Key: 7c9e6679-7425-40de-944b-…" \ -H "Content-Type: application/json" \ -d '{ … }'
Webhook(基盤 → minedia-www)
LiveKit から minedia-www バックエンドへのサーバーイベント(Webhook)の仕様は、専用ページに集約しています。ペイロード例・配信リトライ・冪等性・OpenTok との差分はそちらを参照してください。
エラーコード
機械可読な code(UPPER_CASE)と HTTP ステータスの一覧です。各エンドポイント固有のエラーは、それぞれのエンティティ節にも記載します。
| HTTP | code | 意味 |
|---|---|---|
| 400 | INVALID_REQUEST | 型・必須・enum・範囲・ペア指定などのスキーマ違反 |
| 400 | INVALID_JSON | JSON パース失敗 |
| 400 | UNKNOWN_ROLE | 未知 / 不正なロール値(基盤がゲートキープ) |
| 401 | UNAUTHORIZED | API key 不正・未指定・失効 |
| 403 | FORBIDDEN | 権限不足(truffle 共通)。本書では未使用(他テナント・不存在は L0 で 404) |
| 404 | NOT_FOUND | 不存在 or 他テナント(存在秘匿) |
| 409 | DUPLICATE_EXTERNAL_REF | 同一 external_ref の重複作成 |
| 409 | SESSION_ALREADY_STARTED | 入室実績ありの Session の時間枠変更 |
| 409 | SESSION_RELEASED | 解放済み Session への操作 |
| 409 | PARTICIPANT_BANNED | ban 済み Participant の入室・再入室(入室ゲートの検証で拒否) |
| 409 | RECORDING_NOT_AVAILABLE | 録画が利用可能状態でない |
| 409 | PROCESSED_RECORDING_NOT_AVAILABLE | 加工動画の状態不整合な操作(available 以外への URL 要求・processing 中の削除・pending 以外への complete) |
| 409 | PROCESSED_RECORDING_ALREADY_EXISTS | 加工動画が既に存在する Recording への再 POST(1:1。差替えは DELETE → 再 POST) |
| 409 | UPLOAD_NOT_FOUND | complete 時に S3 へのアップロードが未完(オブジェクト不在) |
| 409 | HANDOUT_IN_USE | 提示中の提示物の削除 / ファイル差し替え |
| 409 | TRANSCRIPTION_IN_PROGRESS | 同一録画・同一言語の文字起こしが処理中 |
| 409 | ROOM_CLOSED | closed の Room への構成更新(PATCH /rooms) |
| 413 | FILE_TOO_LARGE | ファイルサイズ超過 |
| 422 | VALIDATION_ERROR | 業務ルール違反(汎用) |
| 422 | INVALID_TIME_WINDOW | 開始 ≧ 終了、過去終了、上限超過などの時間窓違反 |
| 422 | OUTSIDE_TIME_WINDOW | 入室可能時間外 |
| 422 | UNSUPPORTED_FILE_TYPE | 許可されない拡張子 |
| 422 | IDEMPOTENCY_KEY_CONFLICT | 同一キーで異なるボディ |
| 422 | IMMUTABLE_FIELD | 不変フィールドの変更(Session の external_type / external_id) |
| 429 | RATE_LIMIT_EXCEEDED | レート制限(PoC 未実装・予約) |
Session
インタビューの予約(時間枠)を表すRoom の予約レコードです(1 Room = 1 Session)。調査ドメインの枠(Slot / ExtemporarySession)を external_ref で指します。作成は Room 作成(POST /rooms)の nested session{} として行い、渡すのは予約の語彙(external_ref・時間枠・name)のみ。RTC 構成(archive_* / stream_*)・文字起こし構成は Room 直下のフィールドです。入室口は room.connect_url として露出します。
RTC 構成・文字起こし構成は「呼び出し側テナントが作成時に渡す入力」です。Project / Slot 設定からの解決は minedia-www 側の責務であり、基盤は受け取った値を保存します。
Session オブジェクト
Slot(Project 系)/ ExtemporarySession(即席・www 側実体は extemporary_sessions)null)scheduled(開始前)/ active(時間窓内)/ closed(時間窓終了)/ cancelled(解放済み・テナントによるキャンセル)。DB に実体列はなく、cancelled_at の有無と現在時刻×時間窓 [starts_at−30min, ends_at+30min] からレスポンス生成時に算出(遷移バッチは存在しない)id / session_id / connect_url)。1 Session = 1 Room で常に同時生成{
"id": "0190a1b2-…",
"external_type": "Slot",
"external_id": "123",
"name": null,
"access_token": "k3jH9x…",
"starts_at": "2026-07-01T05:00:00Z",
"ends_at": "2026-07-01T06:00:00Z",
"status": "scheduled",
"room": {
"id": "0190a1b3-…",
"session_id": "0190a1b2-…",
"connect_url": "https://interview.minedia.com/rooms/0190a1b3-…"
},
"created_at": "2026-06-10T09:00:00Z"
}
旧エンティティとの差分(slots / extemporary_slots → sessions)クリックで展開
| 旧フィールド(現行 minedia-www) | 種別 | 新フィールド(sessions) | 備考 |
|---|---|---|---|
| — | 🆕 新設 | id / tenant_id / uuid | 全コアテーブル共通の3 列をまとめた表記(1 つの識別子ではない)。id=内部 BIGINT 主キー(非公開・コア内 FK 用)/ tenant_id=テナントスコープ(PoC は 0 固定)/ uuid=公開識別子(グローバル一意・API では id として露出)。※DB の id と API の id は別物 |
| slots.id / extemporary_slots.id | → 移行 | external_type / external_id | 出所 PK を external_ref 化。例 {"Slot","123"}。FK ではない |
| room_access_token | → 移行 | access_token | グローバル一意・ランダム |
| start_time | → 移行 | starts_at | — |
| end_time | → 移行 | ends_at | — |
| extemporary_slots.name | → 移行 | name | 任意 |
| — (As-Is は中止を調査側 AnswerSlot.status のみで保持) | 🆕 新設 | cancelled_at | 実体列は cancelled_at(datetime nullable)のみ。API 露出の status(scheduled / active / cancelled / closed)は導出値=cancelled_at と時間窓から算出。解放・入室可否の実効ゲートは時間窓+Room.status が担う |
| extemporary_slots.archive_mode / Project 設定 | → 移行 | Room.archive_mode | 呼び出し側が作成時に渡す入力に統一。録画・配信の実行構成は Room 直下のフィールド(Room 参照・Session は純粋な予約) |
| archive_resolution(Project / 即席) | → 移行 | Room.archive_resolution | 640x480 / 1280x720(Room 直下) |
| stream_resolution(Project / 即席) | → 移行 | Room.stream_resolution | Room 直下 |
| stream_frame_rate(Project / 即席) | → 移行 | Room.stream_frame_rate | 30 / 15 / 7 / 1(Room 直下) |
| slots / extemporary_slots.transcription_language | → 移行 | Room.transcription_language | 当初「調査残置」→ G5 で文字起こしコア化に伴いコアへ(default ja-JP)。録画の親が Room のため Room 側で保持 |
| Project.transcription_service | → 移行 | Room.transcription_service | 自動文字起こしの既定エンジン(既定 azure) |
| slots.project_id / limit / segment / memo、extemporary_folder_id / memo | ❌ 対象外 | — (調査に残置) | パネル募集・定員・セグメント・即席フォルダ・メモは調査ドメインの語彙 |
| created_at / updated_at | → 移行 | 同左 | — |
Session を逆引きする
テナント側 PK(Slot.id 等)から基盤の Session を逆引きします(「テナント → 基盤」方向の双方向解決)。逆引き専用で external_type / external_id は必須です(全件一覧は廃止。U-1 決着 2026-06-20)。既定ソートは created_at 降順です。
クエリパラメータ
| パラメータ | 説明 |
|---|---|
| external_type必須 | 逆引きキー(external_id とペア指定) |
| external_id必須 | 逆引きキー(external_type とペア指定) |
| cursor任意 | ページングカーソル |
| limit任意 | 1〜100(default 20) |
エラー
| 条件 | レスポンス |
|---|---|
| external_type / external_id の未指定・片方のみ指定 | 400 INVALID_REQUEST |
curl "…/api/v1/sessions?external_type=Slot&external_id=123" \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
{
"items": [
{ "id": "0190a1b2-…", "external_type": "Slot", "external_id": "123", "status": "scheduled" }
],
"next_cursor": null,
"has_more": false
}
Session を取得する
id(UUID 形式)で 1 件取得します。Room を内包したリソース表現を返します。
エラー
| 条件 | レスポンス |
|---|---|
| 不存在 or 他テナント | 404 NOT_FOUND |
curl …/api/v1/sessions/0190a1b2-… \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
{
"id": "0190a1b2-…",
"external_type": "Slot",
"external_id": "123",
"status": "active",
"room": { "id": "0190a1b3-…" }
}
Session を更新する
作成時のフィールドを部分更新します(external_type / external_id は変更不可。transcription_language / transcription_service は Room の属性のため PATCH /rooms を使う)。JSON Merge Patch(RFC 7396)準拠で、リクエストにキーが存在するフィールドだけ更新します。
更新セマンティクス
| リクエストでの現れ方 | 挙動 |
|---|---|
| キーを省略 | そのフィールドは変更しない |
キーあり・値 null | クリア(null 化)。nullable なのは name のみ |
| キーあり・値あり | その値に更新(作成時のバリデーション適用) |
エラー
| 条件 | レスポンス |
|---|---|
| starts_at / ends_at 変更時、入室実績(EntryHistory)が 1 件以上 | 409 SESSION_ALREADY_STARTED |
| 解放済み(cancelled)Session | 409 SESSION_RELEASED |
| 時間窓 / enum 制約違反 | 400 / 422 |
割付後リスケ不可などは調査ドメイン側の業務ルールです。基盤はトークン・入室実績との整合のみ強制します。
curl -X PATCH …/api/v1/sessions/0190a1b2-… \ -H "Authorization: ApiKey key_live_a1b2.sk_…" \ -H "Content-Type: application/merge-patch+json" \ -d '{ "ends_at": "2026-07-01T06:30:00Z", "name": "コンセプトA 追加ヒアリング" }'
{
"id": "0190a1b2-…",
"name": "コンセプトA 追加ヒアリング",
"ends_at": "2026-07-01T06:30:00Z",
"status": "scheduled"
}
Session を予約キャンセルする
POST /rooms/{id}/close に一本化本エンドポイントは「予約のキャンセル」として残置し、内部で当該 Room の close(status: closed・トークン全失効)へカスケードします。DELETE ですが Room・既発生事実は消えず(物理消去は将来の DELETE /rooms/{id})、振り返り参照は維持されます。
パネルのキャンセル・欠席時に、先行作成した予約(Session)をキャンセルし、Room を close します。
副作用
| 対象 | 挙動 |
|---|---|
| 参加トークン | テナント自己署名のため発行自体は止められない。解放後の入室は検証(入室ゲート)の Session released チェックで拒否 |
| 接続中の参加者 | メディア切断 |
| 既発生の事実データ | 録画・チャット・入退室履歴は削除しない(事後参照可能) |
成功時は 204 No Content。解放済みへの再実行は冪等で 204 を返します。
curl -X DELETE …/api/v1/sessions/0190a1b2-… \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
204 No Content
Room
WebRTC の接続先となる RTC ルームで、参加者・チャット・録画・イベント・入退室を束ねる集約ルート兼作成起点です。kind(interview / connection_test)が用途の権威で、プロバイダ束縛(livekit_session_id / archive_*)を保持します。interview は予約(Session)を nested session{} として同梱し、Room+Session を原子的に同時生成します(接続テストは専用ハンドル POST /connection-tests から生成)。解放は POST /rooms/{id}/close(単一解放点)、物理消去用の DELETE /rooms/{id} は将来予約です。
Room は POST /rooms で作成し、POST /rooms/{id}/close で解放します。interview の予約(Session)は作成リクエストの nested session{} として同時生成され、DELETE /sessions(予約キャンセル)は close へカスケードします。
Room オブジェクト
interview / connection_test。Room の用途を表す権威。付随リソース(session / connection_test)の有無では用途を判別できないため明示列として持つopen / closed。POST /rooms/{id}/close で closed になり入室を締める(閉室後も配下リソースは読み取り可)always / manual(録画モード)640x480 / 1280x720ja-JP)。録画完了後の自動文字起こしと手動起動の既定言語eleven_labs / openai / azure / anyenv⚠ / rimo⚠(既定 azure。⚠=非推奨)。kind=interview でのみ有意味id / external_type / external_id / starts_at / ends_at / status)。kind=interview は 1:1、connection_test / schedule-less は null。DB は sessions.room_id320x240 / 640x480 / 1280x720(既定 320x240)。publisher 設定(OT.initPublisher へ到達)30 / 15 / 7 / 1(既定 15)livekit_session_id(Room SID) から導出。RTC ルーム未生成時は null{
"id": "0190a1b3-…",
"kind": "interview",
"status": "open",
"archive_mode": "always",
"archive_resolution": "640x480",
"stream_resolution": "640x480",
"stream_frame_rate": 15,
"transcription_language": "ja-JP",
"transcription_service": "openai",
"connect_url": "https://interview.minedia.com/rooms/0190a1b3-…",
"provider_inspector_url": "https://cloud.livekit.io/projects/p_1uvc9dsiv14/sessions/RM_UNAo9Dh8Pq9t",
"session": {
"id": "0190a1b2-…",
"external_type": "Slot",
"external_id": "123",
"starts_at": "2026-07-01T05:00:00Z",
"ends_at": "2026-07-01T06:00:00Z",
"status": "scheduled"
},
"created_at": "2026-06-10T09:00:00Z"
}
旧エンティティとの差分(rooms → rooms)クリックで展開
| 旧フィールド | 種別 | 新フィールド | 備考 |
|---|---|---|---|
| — | 🆕 新設 | id / tenant_id / uuid | 全コアテーブル共通の3 列をまとめた表記。id=内部 BIGINT 主キー(非公開)/ tenant_id=テナントスコープ/ uuid=公開識別子(API では id として露出) |
| slot_id / extemporary_slot_id(二股FK) | → 移行 | session_id | 二股 FK を sessions.id へ一本化。出所の多態は Session.external_type が吸収(コア内部 FK) |
| stream_resolution | → 移行 | stream_resolution | default 320x240 |
| stream_frame_rate | → 移行 | stream_frame_rate | default 15 |
| created_at / updated_at | → 移行 | 同左 | — |
Room を作成する
作成起点(Room-first)。kind で用途を決め、interview は nested session{}(時間枠+RTC 構成)を同梱して Room+Session を原子的に同時生成します。connection_test は専用ハンドル POST /connection-tests を使います。
基盤側の副作用(RTC ルーム生成・案A): LiveKit CreateRoom を同期呼び出しして Room SID(RM_…)を取得し livekit_session_id に即保存(set-once)。empty_timeout を枠長 +30 分以上にして 1 Room = 1 SID を担保。CreateRoom 失敗時は Room/Session の作成ごとロールバックし 502 RTC_PROVISION_FAILED(2-phase commit・部分作成を残さない)。Idempotency-Key で安全に再試行できます。
ボディパラメータ
| パラメータ | 説明 |
|---|---|
| kind必須 enum | interview。connection_test は ConnectionTest を使う |
| archive_mode任意 enum | always / manual(default always) |
| archive_resolution任意 enum | 640x480 / 1280x720(default 640x480) |
| stream_resolution任意 enum | 320x240 / 640x480 / 1280x720(default 320x240) |
| stream_frame_rate任意 enum(int) | 30 / 15 / 7 / 1(default 15) |
| transcription_language任意 string(≤16) | 文字起こし言語(BCP 47・default ja-JP。許可値は Transcription 起動 の7コードと同一)。自動文字起こし・手動起動の既定 |
| transcription_service任意 enum | eleven_labs / openai / azure / anyenv⚠ / rimo⚠(default azure。⚠=非推奨・新規は openai / azure / eleven_labs 推奨) |
| sessioninterview は必須 object | 予約の nested オブジェクト(Session オブジェクト参照)。external_type / external_id / name / starts_at / ends_at。connection_test / schedule-less は省略 |
エラー
| 条件 | レスポンス |
|---|---|
| kind 未指定 / enum 不正 / session 必須欠落 | 400 INVALID_REQUEST |
[tenant_id, external_type, external_id] 重複 | 409 DUPLICATE_EXTERNAL_REF |
| starts_at ≧ ends_at / ends_at が過去 / 枠が 24h 超 | 422 INVALID_TIME_WINDOW |
| CreateRoom 同期呼び失敗(RTC 基盤障害) | 502 RTC_PROVISION_FAILED |
成功時は 201 Created と Location: /api/v1/rooms/{room_id} を返します。
curl -X POST …/api/v1/rooms \ -H "Authorization: ApiKey key_live_a1b2.sk_…" \ -H "Idempotency-Key: 7c9e6679-…" \ -H "Content-Type: application/json" \ -d '{ "kind": "interview", "archive_mode": "always", "session": { "external_type": "Slot", "external_id": "123", "starts_at": "2026-07-01T05:00:00Z", "ends_at": "2026-07-01T06:00:00Z" } }'
{
"id": "0190a1b3-…",
"kind": "interview",
"status": "open",
"archive_mode": "always",
"connect_url": "https://interview.minedia.com/rooms/0190a1b3-…",
"session": {
"id": "0190a1b2-…",
"external_type": "Slot",
"external_id": "123",
"starts_at": "2026-07-01T05:00:00Z",
"ends_at": "2026-07-01T06:00:00Z",
"status": "scheduled"
},
"created_at": "2026-06-10T09:00:00Z"
}
Room を取得する
id(UUID 形式)で Room を 1 件取得します。接続先 URL や用途(kind)を参照する用途に使います。
エラー
| 条件 | レスポンス |
|---|---|
| 不存在 or 他テナント | 404 NOT_FOUND |
curl …/api/v1/rooms/0190a1b3-… \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
{
"id": "0190a1b3-…",
"kind": "interview",
"status": "open",
"archive_mode": "always",
"archive_resolution": "640x480",
"stream_resolution": "640x480",
"stream_frame_rate": 15,
"transcription_language": "ja-JP",
"transcription_service": "openai",
"connect_url": "https://interview.minedia.com/rooms/0190a1b3-…",
"provider_inspector_url": "https://cloud.livekit.io/projects/p_1uvc9dsiv14/sessions/RM_UNAo9Dh8Pq9t",
"session": {
"id": "0190a1b2-…",
"external_type": "Slot",
"external_id": "123",
"starts_at": "2026-07-01T05:00:00Z",
"ends_at": "2026-07-01T06:00:00Z",
"status": "scheduled"
},
"created_at": "2026-06-10T09:00:00Z"
}
Room の文字起こし構成を更新する
Room の文字起こし構成(transcription_language / transcription_service)を部分更新します。現行 minedia-www の「Project / ExtemporaryFolder の設定変更 → 配下の枠へ一括反映」は、テナント側が配下の未実施 Room を列挙して個別 PATCH で追随更新する形で踏襲します(解決責務はテナント側)。JSON Merge Patch(RFC 7396)準拠。
ボディパラメータ
| パラメータ | 説明 |
|---|---|
| transcription_language任意 string(≤16) | 文字起こし言語(BCP 47。許可値は Transcription 起動 の7コードと同一)。作成時と同仕様。NOT NULL のため null クリアは不可 |
| transcription_service任意 enum | eleven_labs / openai / azure / anyenv⚠ / rimo⚠。作成時と同仕様 |
エラー
| 条件 | レスポンス |
|---|---|
closed の Room | 409 ROOM_CLOSED |
| 書式 / enum 制約違反 | 400 INVALID_REQUEST |
変更は以後に起動する文字起こし(自動・手動の既定)にのみ効きます。生成済みの Transcription(実行事実の language / provider)は変わりません。archive_* / stream_* の更新可否は R2-3(Session/Room の URL 整理)と併せて後日確定。
curl -X PATCH …/api/v1/rooms/0190a1b3-… \ -H "Authorization: ApiKey key_live_a1b2.sk_…" \ -H "Content-Type: application/merge-patch+json" \ -d '{ "transcription_language": "en-US", "transcription_service": "openai" }'
{
"id": "0190a1b3-…",
"kind": "interview",
"status": "open",
"transcription_language": "en-US",
"transcription_service": "openai"
}
Room を解放する(close)
kind 非依存の単一解放点。Room を status: closed にして入室を締めます。録画・チャット・入退室履歴など既発生の事実は削除せず、閉室後も読み取り可(振り返りフェーズが成立)。物理消去は別動詞(下記)。
副作用
| 対象 | 挙動 |
|---|---|
| Room | status: closed — 以後の入室は検証(Room.status チェック)で拒否・接続中参加者を切断 |
| 録画 | 進行中なら StopEgress → uploaded / available 化(途中録画も成果物として残す) |
| Session | interview は nested Session も closed(時間窓終了)/ cancelled(キャンセル)へ同期。DELETE /sessions はここへカスケード |
DELETE /rooms/{id} は将来予約物理消去(録画含む連鎖 erase・個人情報消去)専用で、close(解放)とは別物。PoC では実装しません(録画単位の消去は DELETE /recordings)。
成功時は 200(冪等。closed 済みへの再実行も 200)。ボディは任意で reason(監査 RoomEvent room.closed)。
curl -X POST …/api/v1/rooms/0190a1b3-…/close \ -H "Authorization: ApiKey key_live_a1b2.sk_…" \ -H "Content-Type: application/json" \ -d '{ "reason": "panelist no-show" }'
{
"id": "0190a1b3-…",
"kind": "interview",
"status": "closed",
"connect_url": "https://interview.minedia.com/rooms/0190a1b3-…",
"session": { "id": "0190a1b2-…", "status": "cancelled" }
}
Participant
「参加者 × 役割」を Room 単位で束縛したリソースです。生成はテナント自己署名 JWT の初回検証時に、クレーム(participant / role / name)から JIT で行われ、直接の作成 API はありません。公開 API は読み取り+個別 banのみで、リアルタイムの参加者一覧はランタイム側が担います。
参加者の身元はテナントが保証し、基盤は per-room の役割束縛・external_ref・表示名スナップショットだけを持ちます。テナント委譲の参加者は external_type / external_id、基盤運用者(minedia_observer)は内部 user_id で表現し、両系統は排他です(どちらも null=匿名入室を許容)。
Participant オブジェクト
id。参加者は Room 単位で束縛されるmoderator / observer / panelist / minedia_observer。署名トークン由来User / Employee)。匿名入室時は nullnullactive / banned。再入室阻止の単一根拠null{
"id": "0190a1d0-…",
"room_id": "0190a1b3-…",
"role": "panelist",
"display_name": "山田太郎",
"external_type": "User",
"external_id": "999",
"status": "active", "banned_at": null,
"created_at": "2026-07-01T04:58:01Z"
}
旧エンティティとの差分(現行の entry_role / chat 由来 → participants)クリックで展開
| 旧フィールド | 種別 | 新フィールド | 備考 |
|---|---|---|---|
| —(専用テーブルなし。参加者 × ルームを初めてテーブル化) | 🆕 新設 | id / tenant_id / uuid | 全コアテーブル共通の3 列をまとめた表記(1 つの識別子ではない)。id=内部 BIGINT 主キー(非公開・コア内 FK 用)/ tenant_id=テナントスコープ(PoC は 0 固定)/ uuid=公開識別子(グローバル一意・API では id として露出) |
| — | 🆕 新設 | room_id | コア内部 FK → rooms.id(NOT NULL)。参加者を所属 Room に束縛 |
| room_entry_histories.entry_role / URL param interview_role | → 移行 | role | moderator / observer / panelist / minedia_observer。クライアント申告 param → 署名トークン由来へ(DEC-03) |
| — | 🆕 新設 | user_id | コア内部 FK → users.id(nullable)。基盤運用者(minedia_observer)の入室時のみ台帳に紐づく |
| room_chat_messages.user_id / employee_id(物理 FK・identity の出所) | → 移行 | external_type / external_id | テナント側 identity。FK ではない(identity FK を external_ref に置換)。匿名は null |
| room_entry_histories.name / room_chat_messages.sender_name | → 移行 | display_name | 表示名スナップショット |
| —(As-Is は強制退出を OpenTok 直叩き・記録なし) | 🆕 新設 | status | active / banned。再入室阻止の単一根拠(§7-1 P5・G1) |
| — | 🆕 新設 | banned_at | BAN の発生時刻(nullable・監査用) |
Participant を一覧する
参加者 × 役割の束縛の事後参照です。見学者一覧・参加実績の突合に使います(リアルタイム一覧はランタイム側)。既定ソートは created_at 昇順です。
クエリパラメータ
| パラメータ | 説明 |
|---|---|
| role任意 | 役割で絞り込み(moderator / observer / panelist / minedia_observer) |
| status任意 | 状態で絞り込み(active / banned) |
| cursor任意 | ページングカーソル |
| limit任意 | 1〜100(default 20) |
エラー
| 条件 | レスポンス |
|---|---|
| room_id が不存在 or 他テナント所有 | 404 NOT_FOUND |
| role / status の enum 不正 | 400 INVALID_REQUEST |
curl "…/api/v1/rooms/0190a1b3-…/participants?role=panelist&status=active" \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
{
"items": [
{
"id": "0190a1d0-…",
"room_id": "0190a1b3-…",
"role": "panelist",
"display_name": "山田太郎",
"external_type": "User",
"external_id": "999",
"status": "active",
"banned_at": null,
"created_at": "2026-07-01T04:58:01Z"
}
],
"next_cursor": null,
"has_more": false
}
Participant を ban する
特定参加者を status: banned にして再入室不可にする操作です(観察者会議・進行中ルームで他者を巻き込まず 1 人だけを締め出す)。Idempotency-Key に対応します。※ban の詳細仕様(再発行トークンへのハードニング等)は後日決定。
副作用
| 対象 | 挙動 |
|---|---|
| 当該 Participant | status を banned に更新 |
| 参加トークン | 既発行のトークンが有効期限内でも、Participant.status の検証で入室を拒否(テナントが再署名しても無効) |
| 接続中の参加者 | メディア切断 |
| 監査 | RoomEvent(participant.banned・payload に participant_id / 理由)として監査記録 |
ボディパラメータ
| パラメータ | 説明 |
|---|---|
| reason必須 string(1..255) | ban 理由(監査用) |
エラー
| 条件 | レスポンス |
|---|---|
| room_id / participant_id が不存在 or 他テナント所有 | 404 NOT_FOUND |
| reason 欠落 / 範囲外(1..255) | 400 INVALID_REQUEST |
成功時は 200 OK。ban 済みへの再実行は冪等で 200 を返します。
connect 時に runtime が status != banned を検証し、ban 済み participant の入室を拒否します。※再発行トークン(新 jti)に対するハードニング(トークン世代による失効等)は本 PoC では未実装。ban の詳細仕様は後日決定。
curl -X POST …/api/v1/rooms/0190a1b3-…/participants/0190a1d0-…/ban \ -H "Authorization: ApiKey key_live_a1b2.sk_…" \ -H "Idempotency-Key: 7c9e6679-…" \ -H "Content-Type: application/json" \ -d '{ "reason": "規約違反のため再入室を禁止" }'
{
"participant_id": "0190a1d0-…",
"status": "banned"
}
EntryHistory
ルームへの入退室+デバイス計測の純ログです(書き込みはランタイム側のみ)。出欠判定(is_attend)の材料となりますが、判定ロジックは調査ドメイン側の責務であり、基盤は事実のみを返します。
EntryHistory は高ボリュームのログ系リソースのため、id はUUID ではなく BIGINT の整数です(コア共通の UUID 規約に対する明示的な例外。カーソルの内部キーにも使用)。フィールド名は他リソースと同じく id ですが、値の形式のみ整数である点に注意してください。
EntryHistory オブジェクト
rooms への参照)null ありmoderator / observer / panelist / minedia_observerChrome)137)iOS)19.1)created_at と同値)null)。entity 側 exited_atleft_at − entered_at)。課金計測・出欠・障害判定の根拠{
"id": 1001,
"room_id": "0190a1b3-…",
"participant_id": "0190a1d0-…",
"name": "山田太郎",
"role": "panelist",
"browser_name": "Chrome",
"browser_version": "137",
"platform_name": "iOS",
"platform_version": "19.1",
"ip_address": "203.0.113.10",
"entered_at": "2026-07-01T04:58:01Z",
"left_at": "2026-07-01T06:01:12Z",
"duration_sec": 3791
}
旧エンティティとの差分(room_entry_histories → entry_histories)クリックで展開
| 旧フィールド(現行 minedia-www) | 種別 | 新フィールド(entry_histories) | 備考 |
|---|---|---|---|
| — | 🆕 新設 | id / tenant_id | ログ系は uuid を持たない純ログ。id=BIGINT 主キー(API でも整数 id として露出・C4 例外)/ tenant_id=テナントスコープ(PoC は 0 固定) |
| room_id | 🆕 新設 | room_id | 旧 integer FK → 新 bigint FK(rooms.id) |
| — | 🆕 新設 | participant_id | FK(participants.id)。匿名入室を許容するため nullable |
| name | → 移行 | name | 表示名スナップショット |
| entry_role | → 移行 | role | integer enum → 同語彙の文字列化(moderator / observer / panelist / minedia_observer) |
| browser_name / browser_version / platform_name / platform_version / ip_address | → 移行 | 同左 | デバイス計測。現状踏襲 |
— (As-Is は退室時刻列なし=created_at のみ) | 🆕 新設 | exited_at | 退室時刻(disconnect webhook で更新・nullable)。入退室をペアで記録。API では left_at として露出 |
| — | 🆕 新設 | duration_sec | 滞在秒数(exited_at − created_at)。課金計測・出欠(is_attend)・障害(録画欠落)判定の根拠 |
| created_at / updated_at | → 移行 | 同左 | entered_at = created_at(現 entry_time メソッド踏襲) |
EntryHistory を一覧する
指定 Room の入退室履歴を一覧します。出欠判定(is_attend)の材料として調査ドメイン側が pull します。既定ソートは entered_at 昇順です。
クエリパラメータ
| パラメータ | 説明 |
|---|---|
| role任意 | 役割で絞り込み(moderator / observer / panelist / minedia_observer) |
| cursor任意 | ページングカーソル |
| limit任意 | 1〜100(default 20) |
curl "…/api/v1/rooms/0190a1b3-…/entry-histories?role=panelist" \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
{
"items": [
{
"id": 1001,
"room_id": "0190a1b3-…",
"participant_id": "0190a1d0-…",
"name": "山田太郎",
"role": "panelist",
"browser_name": "Chrome",
"browser_version": "137",
"platform_name": "iOS",
"platform_version": "19.1",
"ip_address": "203.0.113.10",
"entered_at": "2026-07-01T04:58:01Z",
"left_at": "2026-07-01T06:01:12Z"
}
],
"next_cursor": null,
"has_more": false
}
W1 participant.entered
ルーム内の入室の事実を push 通知します。Participant 単体の入退室 Webhook はなく、入室の事実は EntryHistory の W1 が担い、data に Participant を内包します。退室の push 通知はありません——退室の事実は EntryHistory(left_at / duration_sec)に記録され、GET /rooms/{room_id}/entry-histories の pull で参照します。
| type | 用途 |
|---|---|
participant.entered W1 | 入室。出欠判定の材料(現行 OpenTok callback の touch_interview_attendance 後継) |
退室理由 leave_reason の取り扱いは後日意思決定です。現行は connectionDestroyed を noop で捨てており退室理由を一切保持していません。採用時は EntryHistory に leave_reason(voluntary / kicked / disconnected / session_ended)を追加し、ban は kicked として表現します。
{
"id": "evt_0190a210-…",
"version": "1",
"type": "participant.entered",
"occurred_at": "2026-07-01T04:58:01Z",
"data": {
"room_id": "0190a1b3-…",
"session_id": "0190a1b2-…",
"session_external_ref": { "external_type": "Slot", "external_id": "123" },
"participant": {
"id": "0190a1d0-…",
"role": "panelist",
"display_name": "山田太郎",
"external_type": "User",
"external_id": "999"
},
"entry_history_id": 1001,
"entered_at": "2026-07-01T04:58:01Z"
}
}
Chat
チャットの送受信はランタイム側の責務です。公開 API は振り返りフェーズの事後参照のみを提供します(チャット履歴の持ち手=基盤)。公開範囲(visibility)は ChatMessage 自身の列で、クエリ条件として露出します。
チャットは chat_messages の単一テーブルです。「チャネル」という保存実体は持ちません — 配信先はインタビュールームが送信のたびに role × visibility から算出(サーバー権威型)するため、visibility はメッセージ自身が持つ配信ポリシーのラベルです。添付ファイルも独立エンティティではなく content_type=file のメッセージとして同居し、事後参照は本 API の attachment.url(都度署名)で解決します。CSV ダウンロード等の帳票整形(列名の日本語化・タイムゾーン・ファイル名)はテナント側の責務で、テナントの BFF が本履歴 API を全ページ取得して生成します(基盤に CSV 応答・エクスポート API はありません)。
ChatMessage オブジェクト
id で吸収)。基盤採番 UUIDv7 原則に対するチャットのみの例外public / private / observer_only。送信可否・配信範囲は role × visibility を基盤の RoomPolicy がサーバー側で判定id。system メッセージは nulltext / stamp / archive_status / file(添付)stamp はスタンプ識別子、file はキャプション or nullcontent_type=file のみ。url(取得都度生成の短期署名 GET URL・S3 キーは非公開)/ name / mime / size{
"id": "0190a1e5-…",
"visibility": "public",
"participant_id": "0190a1d0-…",
"sender_name": "山田太郎",
"content_type": "text",
"text": "よろしくお願いします",
"attachment": null,
"created_at": "2026-07-01T05:00:30Z"
}
定型スタンプ(content_type=stamp)
スタンプはマスタテーブルを持たず、ChatMessage.text にスタンプ識別子を格納します。識別子は visibility セットスコープの短い名前(現行 minedia-www が保存している I18n キー形式 chat.{channel}.{name}_stamp は持ち込みません)。スタンプの集合は visibility × 送信者 role の組み合わせごとに基盤の設定ファイルで定義され、送信時の存在検証(未知の識別子は 422 INVALID_STAMP)と、ルーム内の一覧取得(GET /rooms/{room_id}/chat-stamps・参加 JWT・インタビュールーム)に使われます。表示ラベル(日本語化・多言語化)はテナント側の責務です。
初期セット(13 種・現行 minedia-www の送信 UI を 1:1 移植)
| visibility | 送信 role | 識別子 | 既定ラベル(ja) |
|---|---|---|---|
private | moderator | ok | 了解しました。 |
ask_later | 後で聞きます。 | ||
anything_else | 他にありますか。 | ||
private | minedia_observer | guide | ご案内いたします。 |
call | お電話いたします。 | ||
please_wait | ご案内中です。少々お待ち下さい。 | ||
reload | 再読み込みのご案内お願い致します。 | ||
observer_only | observer / minedia_observer | nice | いいね |
ok | OK | ||
i_see | なるほど | ||
a_lot | そんなに | ||
public | minedia_observer | please_reload | 再読み込みしてください |
calling | 電話致します |
識別子は visibility セット内で一意で、ok は private(了解しました。)と observer_only(OK)に別ラベルで併存します。履歴の読者はメッセージの visibility と併せて解決してください。panelist はスタンプ送信不可(現行踏襲)。現行の送信 UI から廃止済みの過去キー(summarize=まとめてください/pindown=詳しくお願いします)は初期セットに含めず、移行データにのみ現れる語彙として扱います。テナント別カスタマイズが必要になった時点でマスタテーブル化を検討します(PoC は設定ファイル固定)。
旧エンティティとの差分(room_chat_channels / room_chat_messages / room_chat_posts → chat_messages 単一テーブル)クリックで展開
chat_messages(現 RoomChatChannel + RoomChatMessage + RoomChatPost の 3 テーブルを単一テーブルへ集約)
| 旧フィールド(現行 minedia-www) | 種別 | 新フィールド(chat_messages) | 備考 |
|---|---|---|---|
| — | 🆕 新設 | id / tenant_id / uuid | 共通 3 列。ただし uuid は送信クライアントが採番(冪等キー・unique [uuid])し、API では id(UUID 形式)として露出 |
| room_chat_messages.room_chat_channel_id (チャネル経由) | → 移行 | room_id | FK → rooms.id(Room 直下。チャネル実体は持たない) |
| room_chat_channels.usage (integer enum) | → 移行 | visibility | 文字列化してメッセージ自身の列に。public / private / observer_only |
| user_id / employee_id (物理FK) | → 移行 | participant_id | identity 物理FK をコア内参加者 FK に置換(nullable・system メッセージ用) |
| content_type | → 移行 | content_type | text / stamp / archive_status / file(添付=旧 RoomChatPost の統合) |
| text | → 移行 | text | — |
| sender_id (global_id 相当) | → 移行 | sender_ref | テナント不透明文字列の表示用送信者 ID |
| sender_name | → 移行 | sender_name | 送信時点のスナップショット |
| room_chat_posts.file (別テーブル・text=URL の暗黙リンク。現行キー: {env}/uploads/room_chat_post/file/{id}/{ファイル名} @ com-minedia-minedia) | → 移行 | attachment_key / attachment_name / attachment_mime / attachment_size | content_type=file のみ。S3 オブジェクトキーを保存(URL は保存せず都度署名)。現行の「post 作成 → text=file_url の message 別途作成」という 2 レコード暗黙リンク、および text に焼き込んだ署名 URL が 3 時間で失効する問題を解消 |
チャット履歴を取得する
振り返りフェーズの事後参照用に、ルームのチャット履歴を取得します。サーバー間参照のため全 visibility を返せます。エンドユーザー(クライアント / オペレーター)への表示可否(private / observer_only の閲覧制限)はテナント側の認可責務です。既定ソートは created_at 昇順です。
クエリパラメータ
| パラメータ | 説明 |
|---|---|
| visibility任意 | 絞り込み。public / private / observer_only。未指定は全メッセージ |
| cursor任意 | ページングカーソル |
| limit任意 | 1〜100(default 20) |
エラー
| 条件 | レスポンス |
|---|---|
| 不存在 or 他テナントの room | 404 NOT_FOUND |
| visibility が enum 外 | 400 INVALID_REQUEST |
curl "…/api/v1/rooms/0190a1b3-…/chat-messages?visibility=public&limit=20" \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
{
"items": [
{
"id": "0190a1e5-…",
"visibility": "public",
"participant_id": "0190a1d0-…",
"sender_name": "山田太郎",
"content_type": "text",
"text": "よろしくお願いします",
"attachment": null,
"created_at": "2026-07-01T05:00:30Z"
},
{
"id": "0190a1e6-…",
"visibility": "public",
"participant_id": "0190a1d1-…",
"sender_name": "モデレーター佐藤",
"content_type": "file",
"text": null,
"attachment": { "url": "https://s3…(短期署名)", "name": "資料.pdf", "mime": "application/pdf", "size": 1048576 },
"created_at": "2026-07-01T05:01:10Z"
}
],
"next_cursor": null,
"has_more": false
}
チャット履歴を削除する(運用 purge)
ルームのチャット履歴をまとめて削除します(現行 operator 画面の destroy_messages 後継。誤投稿・個人情報の消去等の運用操作)。チャット履歴を基盤が所有するため、削除も基盤側 API で行います。Idempotency-Key に対応します。
クエリパラメータ
| パラメータ | 説明 |
|---|---|
| visibility任意 | 絞り込み。public / private / observer_only。未指定はルームの全メッセージを対象 |
副作用
| 対象 | 挙動 |
|---|---|
| 対象メッセージ | 対象範囲の ChatMessage を削除(room_id 直下の単一テーブルで完結) |
| 添付 | content_type=file の S3 オブジェクトを attachment_key に基づき非同期削除(キュー経由) |
| 監査 | RoomEvent chat.purged(payload に件数・要求元)を記録 |
成功時は 204 No Content。対象 0 件でも冪等で 204 を返します。
個別削除は PoC では非対応です(必要なら v2 で DELETE …/chat-messages/{id} を追補する余地があります)。
curl -X DELETE "…/api/v1/rooms/0190a1b3-…/chat-messages?visibility=public" \ -H "Authorization: ApiKey key_live_a1b2.sk_…" \ -H "Idempotency-Key: 7c9e6679-…"
204 No Content
RoomEvent
Room 内で発生した事実を記録する汎用イベントログです。書き込み(提示物の show / hide 等)はランタイム側の能力であり、公開 API は読み取りのみを提供します(URL レポート・再生・監査用)。
RoomEvent は高ボリュームなログ系リソース(C4)のため、UUID を持たず BIGINT の整数 を id として露出します(カーソルの内部キーにも使用)。UUID 文字列を id とする通常リソースに対する明示的な例外です。
RoomEvent オブジェクト
idhandout.show)に加え、未知の値はテナント拡張として不透明文字列で保持(enum 化しない)handout.show の handout_id)。未設定時は null{
"id": 9001,
"room_id": "0190a1b3-…",
"event_type": "handout.show",
"payload": {
"handout_id": "0190a1c0-…"
},
"created_at": "2026-07-01T05:10:00Z"
}
旧エンティティとの差分(room_events → room_events)クリックで展開
| 旧フィールド(現行 minedia-www) | 種別 | 新フィールド(room_events) | 備考 |
|---|---|---|---|
| — | 🆕 新設 | id / tenant_id | 共通規約(uuid なし・純ログ)。id はログ系のため BIGINT の整数(カーソルの内部キーにも使用)/ tenant_id=テナントスコープ(PoC は 0 固定) |
| room_id | → 移行 | room_id | FK → rooms.id |
| type | → 移行 | event_type | STI 無効化用の type をリネーム(現 inheritance_column = :_type_disabled)。SHOW_PROJECT_HANDOUT 等の調査固有語彙は不透明文字列のまま |
| payload | → 移行 | payload | text (JSON serialize) → json / text |
| created_at / updated_at | → 移行 | 同左 | — |
RoomEvent を一覧する
Room 配下のイベントを時系列で取得します。再生ロジック(SHOW / HIDE 圧縮)の帰属は未決のため、本 API は生イベントの時系列のみ返します。既定ソートは created_at 昇順です。
クエリパラメータ
| パラメータ | 説明 |
|---|---|
| event_type任意 | イベント種別での絞り込み(例 handout.show) |
| cursor任意 | ページングカーソル |
| limit任意 | 1〜100(default 20) |
エラー
| 条件 | レスポンス |
|---|---|
| Room が不存在 or 他テナント | 404 NOT_FOUND |
コア語彙の例: handout.show / handout.hide / recording.failed / chat.purged。未知の値はテナント拡張として不透明文字列で保持します。
curl "…/api/v1/rooms/0190a1b3-…/room-events?event_type=handout.show" \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
{
"items": [
{
"id": 9001,
"room_id": "0190a1b3-…",
"event_type": "handout.show",
"payload": { "handout_id": "0190a1c0-…" },
"created_at": "2026-07-01T05:10:00Z"
}
],
"next_cursor": null,
"has_more": false
}
Recording
録画の事実を表すリソースです。録画の開始 / 停止はランタイム側の能力(recording.control=moderator)または archive_mode: always の自動録画で行われ、公開 API は録画の事実の読み取り+署名 URL の払い出し+運用削除のみを提供します。
RtcSession(エンジン抽象)は Room に統合され廃止(2026-06-29)。livekit_session_id / archive_* は Room の列となり、Recording は room_id で Room に直属します。文字起こしはコア化されており(G5)、録画完了後の自動 / 手動文字起こしは Transcription ページを参照してください。
Recording オブジェクト
rtc_session_id。RtcSession は Room に統合)。接続テスト用録画は kind=connection_test の Room の room_id を持つ。RTC プロバイダ束縛は Room 側を参照(録画側の非正規化列は廃止)started / paused / stopped / uploaded / available / expired / failed。署名 URL 払い出しは available のみcomposed(合成)/ individual(個別)started / paused)など未確定時は null(確定は uploaded / available 以降。entity-design §3.6 と一致)null(確定は uploaded / available 以降。entity-design §3.6 と一致){
"id": "0190a1f0-…",
"room_id": "0190a1b3-…",
"provider_archive_id": "EG_xxxx",
"status": "available",
"output_mode": "composed",
"size": 734003200,
"duration_sec": 3612.5,
"created_at": "2026-07-01T05:00:05Z",
"updated_at": "2026-07-01T06:05:00Z"
}
旧エンティティとの差分(ot_archives → recordings)クリックで展開
| 旧フィールド | 種別 | 新フィールド | 備考 |
|---|---|---|---|
| — | 🆕 新設 | id / tenant_id / uuid | 全コアテーブル共通の3 列をまとめた表記。id=内部 BIGINT 主キー(非公開)/ tenant_id=テナントスコープ(PoC は 0 固定)/ uuid=公開識別子(API では id として露出・調査側の録画参照キー) |
| ot_session_id | → 移行 | room_id | コア内部 FK → rooms.id(RtcSession を Room に統合・2026-06-29。egress webhook の roomName/SID → Room で解決) |
| —(旧 engine 非正規化) | ❌ 削除 | — | プロバイダ束縛は Room が保持。録画側の非正規化列は冗長のため廃止 |
| archive_id(OpenTok) | → 移行 | provider_archive_id | 汎用名へ(LiveKit=Egress ID / Zoom=recording ID) |
| status | → 移行 | status | started / paused / stopped / uploaded / available / expired / failed。DEC-02 確定時に「正規化 status+provider_status」2 層化候補 |
| output_mode | → 移行 | output_mode | composed / individual |
| size | → 移行 | size | bytes |
| file_duration_sec | → 移行 | duration_sec | decimal(10,4) |
| duration_from_tokbox | ❌ 対象外 | — | ベンダ固有の生値・持ち込まない |
| #video_s3_key(メソッド+Settings.opentok) | → 移行 | storage_key | 生成ロジックはコアへ移植、bucket / apikey はテナント設定経由 |
| created_at / updated_at | → 移行 | 同左 | — |
Room の録画を一覧する
Room に紐づく Recording を返します(1 Room に複数 Recording あり得ます)。既定ソートは created_at 昇順です。接続テスト用録画は kind=connection_test の Room 配下にあり、インタビュー Room の本エンドポイントには現れません(被験者の接続テスト録画は ConnectionTest の recordings から辿ります)。
クエリパラメータ
| パラメータ | 説明 |
|---|---|
| cursor任意 | ページングカーソル |
| limit任意 | 1〜100(default 20) |
エラー
| 条件 | レスポンス |
|---|---|
| Room が不存在 or 他テナント | 404 NOT_FOUND |
curl "…/api/v1/rooms/0190a1b3-…/recordings" \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
{
"items": [
{
"id": "0190a1f0-…",
"room_id": "0190a1b3-…",
"status": "available",
"output_mode": "composed",
"duration_sec": 3612.5
}
],
"next_cursor": null,
"has_more": false
}
Recording を取得する
id(UUID 形式)で録画の事実を 1 件取得します。録画の実体ファイルへのアクセスは署名付き URL の払い出し(次節)を使います。
エラー
| 条件 | レスポンス |
|---|---|
| 不存在 or 他テナント | 404 NOT_FOUND |
curl …/api/v1/recordings/0190a1f0-… \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
{
"id": "0190a1f0-…",
"room_id": "0190a1b3-…",
"provider_archive_id": "EG_xxxx",
"status": "available",
"output_mode": "composed",
"size": 734003200,
"duration_sec": 3612.5,
"created_at": "2026-07-01T05:00:05Z",
"updated_at": "2026-07-01T06:05:00Z"
}
署名付き URL を払い出す
録画ファイル(S3・非公開バケット)への短期署名 URL を払い出します。文字起こし処理・録画閲覧画面の双方で使います。
基盤は「API key 認証済みテナント」(server-to-server)にのみ署名 URL を払い出します(参加 JWT には払い出しません)。エンドユーザー(クラ / オペ)への最終的な閲覧可否はテナント側が判断します。基盤は recording.access の role 強制を持ち込まず(CanCanCan=調査ドメイン / RoomPolicy=ルーム内の責務分界を保つ)、責務は「払い出し+アクセスログ」に限定。録画=個人情報のため、払い出しは基盤側でアクセスログを記録します(recording_id / 払い出し時刻 / expires_at / 要求元テナント)。
クエリパラメータ
| パラメータ | 説明 |
|---|---|
| expires_in_seconds任意 integer | 署名 URL の有効秒数。60〜10800(default 3600) |
エラー
| 条件 | レスポンス |
|---|---|
status が available 以外 | 409 RECORDING_NOT_AVAILABLE |
expires_in_seconds が範囲外 | 400 INVALID_REQUEST |
| 不存在 or 他テナント | 404 NOT_FOUND |
curl "…/api/v1/recordings/0190a1f0-…/download-url?expires_in_seconds=3600" \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
{
"url": "https://s3.../signed...",
"expires_at": "2026-07-01T07:05:00Z"
}
Recording を削除する(運用・個人情報消去)
録画の実体(S3 オブジェクト)と事実レコードを削除します(現行 operator 画面の DELETE ot_archives/:id 後継)。録画=個人情報のため、保持期間超過・誤録画・削除依頼への対応として基盤側で物理削除します。Idempotency-Key ヘッダに対応します。
副作用
| 対象 | 挙動 |
|---|---|
| 録画ファイル / レコード | S3 の録画ファイルと Recording レコードを削除 |
| Transcription | 当該録画にぶら下がる Transcription も連鎖削除 |
| 監査ログ | 削除の事実を基盤側で記録(recording_id / 削除時刻 / 要求元テナント) |
エラー
| 条件 | レスポンス |
|---|---|
録画中(status が started / paused)は削除不可(停止・完了後に削除) | 409 RECORDING_NOT_AVAILABLE |
成功時は 204 No Content。削除済みへの再実行は冪等で 204 を返します。
curl -X DELETE …/api/v1/recordings/0190a1f0-… \ -H "Authorization: ApiKey key_live_a1b2.sk_…" \ -H "Idempotency-Key: 7c9e6679-…"
204 No Content
W3 recording.status_changed
録画の状態が変化したときに送信されます。available への遷移は文字起こし・事後処理の起点として、failed への遷移は録画障害アラート・オペ対応の起点として利用できます(現行 app_main の録画監視は基盤側へ移り、通知だけ調査側に飛びます)。
| status | 用途 |
|---|---|
available | 文字起こし・事後処理の起点 |
failed | 録画障害アラート・オペ対応(failure_reason に原因) |
{
"id": "evt_0190a210-…",
"version": "1",
"type": "recording.status_changed",
"occurred_at": "2026-07-01T06:05:00Z",
"data": {
"recording_id": "0190a1f0-…",
"room_id": "0190a1b3-…",
"session_id": "0190a1b2-…",
"session_external_ref": { "external_type": "Slot", "external_id": "123" },
"status": "available",
"previous_status": "uploaded",
"failure_reason": null
}
}
ProcessedRecording
録画から生成する加工済み動画です。トリム指示 API + Workflows によるトリム処理(source=trim)と、人手で加工した動画の手動アップロード(source=upload・S3 署名付き PUT + complete)の 2 系統を持ちます。Recording と 1:1 で、パスはネストしたシングルトン(/recordings/{recording_id}/processed-recording・単数形)です。
コアが記録するのは source=trim/upload と加工済みファイルの事実のみです。個人情報保護加工(ブラー / 音声変換)の依頼管理(要否判断・Slack 依頼・依頼中 / 完了の状態)は調査ドメインの関心事として minedia-www 側に残置します(minedia-www 変更点 §3.1)。ブラー / 音声変換の自動化は対象外(現行も未自動化・将来拡張)。録画の外部共有(OtArchiveAccess)も調査残置で、閲覧可否はテナント側が判断します(DEC-08 と同じ分界)。
出発点は Workers のサイズ上限です。基盤コアは Cloudflare Workers 上で動作し、リクエストボディの上限は Free/Pro 100MB・Business 200MB・Enterprise 500MB(アカウントプラン依存・超過は 413。Workers Limits — Request limits)。一方、動画は数百 MB〜GB 級で(現行 minedia-www も 100MB パート分割のマルチパートアップロードを実装済み=大容量が常態)、ファイル本体をコアのエンドポイントで受け取る形式(multipart POST や PATCH での差替え)はサイズ上限で成立しません。よってファイル転送は「S3 への直接 PUT(署名付き URL)」の一択になります。
complete はその帰結として必要になります。S3 直接 PUT はコアを経由しないため、コアからは転送の開始・完了・中断を観測できません。テナントが complete で「転送完了」を合図し、コアが S3 HEAD で実在と size を検証してはじめて available に遷移し W7 を発火できます。合図なしでは pending を確定できず、アップロード途中の不完全なファイルに download-url を払い出すリスクが残ります。duration_sec をテナント申告で受けるのも同じ制約(Workers では ffprobe 相当を実行できない)に由来します。
PATCH ではなく POST アクションなのは、これが「フィールド更新」ではなくサーバー検証付きの状態遷移だからです。POST /rooms/{id}/close・POST …/ban と同じ「副作用を伴う操作=POST サブリソース」の語彙で表現しています(S3 の CompleteMultipartUpload など presigned 方式の API に共通する定石でもあります)。
ProcessedRecording オブジェクト
trim(Workflows によるトリム処理)/ upload(手動アップロード)source=trim のトリム範囲(秒)。upload は nullpending →(processing →)available / failed。trim は Workflows が遷移させ、upload は complete で available 化。署名 URL 払い出しは available のみavailable 以降に確定。upload は S3 実測値)trim は Workflows が確定、upload は complete でのテナント申告値(検証不能な参考値・未申告は null)failed 時の要約理由(内部エラー詳細は非公開・Transcription.error_message と同方針){
"id": "0190a220-…",
"recording_id": "0190a1f0-…",
"source": "trim",
"trim_start_sec": 12.0,
"trim_end_sec": 340.5,
"status": "available",
"size": 51200000,
"duration_sec": 328.5,
"failure_reason": null,
"created_at": "2026-07-01T06:20:00Z",
"updated_at": "2026-07-01T06:22:10Z"
}
旧エンティティとの差分(processed_ot_archives → processed_recordings)クリックで展開
| 旧フィールド | 種別 | 新フィールド | 備考 |
|---|---|---|---|
| — | 🆕 新設 | id / tenant_id / uuid | 全コアテーブル共通の 3 列(uuid は API では id として露出・調査側の参照キー) |
| ot_archive_id | → 移行 | recording_id | コア内部 FK → recordings.id。現行 has_one(1:1)を踏襲し unique |
| processed_information(trim の trim_time) | → 移行 | trim_start_sec / trim_end_sec | JSON 内のトリム範囲を専用列へ構造化 |
| processed_information(blur / voice_change の依頼記録) | ❌ 調査残置 | — | 個人情報保護加工の依頼管理は調査ドメインへ(minedia-www 側に依頼レコードを新設)。コアは加工内容のメタを持たない |
| status(unprocessed / started / processing / completed / expired / failed) | → 移行 | status(pending / processing / available / failed) | unprocessed / started / expired は依頼管理側の概念のため持ち込まない。completed は Recording と同じ available 語彙へ |
| — | 🆕 新設 | source | trim / upload の生成経路 |
| — | 🆕 新設 | failure_reason | W7 通知用の要約理由 |
#video_s3_key(固定キー processed_archive.mp4 へ上書き) | → 移行 | storage_key | 非公開(署名 URL で払い出し・Recording.storage_key と同方針) |
| #manual_upload_s3_video!(Rails サーバー経由アップロード) | → 移行 | 署名付き PUT + complete | Workers のボディ上限 100MB に対し録画は数百 MB〜GB 級のため、S3 直接 PUT 方式へ |
| #destroy_video!(ファイルのみ削除・status 巻き戻し) | ❌ 廃止 | — | 差替えは DELETE(レコードごと削除)→ 再 POST に統一 |
| started_at / completed_at | ❌ 対象外 | — | created_at / updated_at と status 遷移で代替 |
トリム指示 / アップロードを開始する
source=trim はトリム範囲を指定して Workflows の非同期処理を起動します(完了 / 失敗は W7)。source=upload は加工済みファイルの受け渡しを開始し、レスポンスの署名付き PUT URL にテナントが S3 直接 PUT(単一 PUT・上限 5GB)した後、complete で確定します。Idempotency-Key に対応します。
Recording と 1:1 のため、既に存在する場合の再 POST は 409 です。現行の「削除 → 再アップロード」の 2 段階運用と同型で、PATCH は設けません。PUT URL の期限切れ等で放置された pending は基盤側で定期クリーンアップします。
リクエストボディ
| フィールド | 説明 |
|---|---|
| source必須 | trim / upload |
| trim_start_sec / trim_end_sectrim は必須 | トリム範囲(秒)。upload では指定不可 |
エラー
| 条件 | レスポンス |
|---|---|
元 Recording が available 以外 | 409 RECORDING_NOT_AVAILABLE |
| 既に ProcessedRecording が存在 | 409 PROCESSED_RECORDING_ALREADY_EXISTS |
0 ≤ trim_start_sec < trim_end_sec 違反(ペア整合) | 400 INVALID_REQUEST |
trim_end_sec が録画の長さを超過 | 422 VALIDATION_ERROR |
curl -X POST …/api/v1/recordings/0190a1f0-…/processed-recording \ -H "Authorization: ApiKey key_live_a1b2.sk_…" \ -H "Idempotency-Key: 7c9e6679-…" \ -H "Content-Type: application/json" \ -d '{ "source": "trim", "trim_start_sec": 12.0, "trim_end_sec": 340.5 }'
{
"id": "0190a220-…",
"recording_id": "0190a1f0-…",
"source": "upload",
"status": "pending",
"upload": {
"url": "https://s3.ap-northeast-1.amazonaws.com/…signed-put…",
"expires_at": "2026-07-01T07:20:00Z"
}
}
アップロードを完了する(complete)
source=upload の確定操作です。S3 への直接 PUT はコアを経由せず観測できないため、テナントがこのエンドポイントで「転送完了」を合図します。基盤が S3 HEAD でオブジェクトの実在と size を確認して available 化します(W7 発火)。設計理由の詳細はページ冒頭の note を参照してください。
リクエストボディ
| フィールド | 説明 |
|---|---|
| duration_sec任意 | 加工後の再生時間(秒)。基盤コア(Workers)は ffprobe 相当を実行できないためテナント申告で受領(未指定は null) |
エラー
| 条件 | レスポンス |
|---|---|
source が upload 以外、または status が pending 以外 | 409 PROCESSED_RECORDING_NOT_AVAILABLE |
| S3 にオブジェクトが不在(PUT 未完) | 409 UPLOAD_NOT_FOUND |
curl -X POST …/api/v1/recordings/0190a1f0-…/processed-recording/complete \ -H "Authorization: ApiKey key_live_a1b2.sk_…" \ -H "Content-Type: application/json" \ -d '{ "duration_sec": 328.5 }'
{
"id": "0190a220-…",
"recording_id": "0190a1f0-…",
"source": "upload",
"status": "available",
"size": 51200000,
"duration_sec": 328.5
}
ProcessedRecording を取得する
Recording に紐づく加工動画を 1 件取得します(1:1 のため一覧 API はありません)。
エラー
| 条件 | レスポンス |
|---|---|
| 未作成 / Recording が不存在 or 他テナント | 404 NOT_FOUND |
curl …/api/v1/recordings/0190a1f0-…/processed-recording \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
署名付き URL を払い出す
加工済みファイル(S3・非公開バケット)への短期署名 URL を払い出します。クエリ(expires_in_seconds)・認可の責務分界(DEC-08:払い出しは API key 認証済みテナントサーバー限定・エンドユーザーへの閲覧可否はテナント側)・アクセスログは Recording の署名 URL 払い出しと同一です。
エラー
| 条件 | レスポンス |
|---|---|
status が available 以外 | 409 PROCESSED_RECORDING_NOT_AVAILABLE |
{
"url": "https://s3…/signed…",
"expires_at": "2026-07-01T07:22:10Z"
}
ProcessedRecording を削除する
S3 オブジェクトとレコードの両方を削除します(監査ログ記録は Recording の削除と同方針)。差替えの前段としても使います(DELETE → 再 POST)。
エラー
| 条件 | レスポンス |
|---|---|
status が processing(Workflows 実行中) | 409 PROCESSED_RECORDING_NOT_AVAILABLE |
204 No Content # 削除済み・未作成への再実行は冪等で 204
W7 processed_recording.status_changed
加工動画の状態が available / failed に遷移したときに送信されます。source=upload の complete 成功時にも発火するため、minedia-www 側の依頼管理レコードの完了検知に使えます。取りこぼしの補填は GET …/processed-recording のポーリングで行います。
| status | 用途 |
|---|---|
available | 加工済み動画の閲覧・外部共有の有効化、依頼管理レコードの完了化 |
failed | オペ対応(現行の Sidekiq リトライ失効 → Slack 通知の後継。minedia-www 側で受けて通知) |
{
"id": "evt_0190a230-…",
"version": "1",
"type": "processed_recording.status_changed",
"occurred_at": "2026-07-01T06:22:10Z",
"data": {
"processed_recording_id": "0190a220-…",
"recording_id": "0190a1f0-…",
"room_id": "0190a1b3-…",
"session_external_ref": { "external_type": "Slot", "external_id": "123" },
"source": "trim",
"status": "available",
"previous_status": "processing",
"failure_reason": null
}
}
Transcription
2026-06-18 の G5 決定でコア化した、録画の子リソースです。基盤は文字起こしの事実+結果を持ち、要約・集計は調査ドメインが Transcription.id を起点に行います。公開 API は読み取り+完了 Webhookを基本としつつ、加えて手動起動を持ちます。
録画完了時に基盤が自動で文字起こしを実行します。言語・エンジンの既定は Room の transcription_language / transcription_service(既定 ja-JP / azure)。手動起動は、別言語・再実行・翻訳をオペレーション起点でオンデマンド要求するための後継機能です(read-only 原則に対する運用例外)。
Transcription オブジェクト
Recording.id)。1 録画に複数言語/翻訳がぶら下がるRoom.id)eleven_labs / openai / azure / anyenv / rimonull)sending_request / processing / retrying / done / error / request_errorja-JP)transcription=自前文字起こし / translation=翻訳(旧 local: boolean を enum 化)status=done のときのみ非 null)。本体(result_json)は基盤内部(DB 行内格納・As-Is 踏襲)に閉じ、API は署名 URL で払い出す(実体は下記「本文を取得する」){
"id": "0190a210-…",
"recording_id": "0190a1f0-…",
"room_id": "0190a1b3-…",
"provider": "openai",
"provider_transcription_id": "tr_xxxx",
"status": "done",
"language": "ja-JP",
"kind": "transcription",
"result_url": "https://.../signed-vtt…",
"created_at": "2026-07-01T06:10:00Z",
"updated_at": "2026-07-01T06:14:30Z"
}
旧エンティティとの差分(transcription_requests → transcriptions)クリックで展開
| 旧フィールド(現行 minedia-www) | 種別 | 新フィールド(transcriptions) | 備考 |
|---|---|---|---|
| — | 🆕 新設 | id / tenant_id / uuid | 全コアテーブル共通の3 列をまとめた表記(1 つの識別子ではない)。id=内部 BIGINT 主キー(非公開・コア内 FK 用)/ tenant_id=テナントスコープ(PoC は 0 固定)/ uuid=公開識別子(API では id として露出・調査側の要約/集計の参照キー)。※DB の id と API の id は別物 |
| ot_archive_id | → 移行 | recording_id | コア内部 FK → recordings.id(OtArchive → Recording に追随) |
| transcription_service(5 ベンダ integer enum) | → 移行 | provider | eleven_labs / openai / azure / anyenv / rimo。ベンダ中立の文字列化 |
| transcription_id(ベンダのジョブ ID) | → 移行 | provider_transcription_id | 汎用名へ。unique [provider, provider_transcription_id] |
| status(6 種 integer enum) | → 移行 | status | sending_request / processing / retrying / done / error / request_error(文字列化) |
| result_json | → 移行 | result_json | VTT/JSON の文字起こし結果(ベンダ生形式はここに閉じる)。API は result_url で払い出す(本体は基盤内部) |
| duration | ❌ 対象外 | — | 参照箇所ゼロの write-only 列。録画の長さは Recording.duration_sec と重複するため持ち込まない |
| language | → 移行 | language | G5 副作用でコアに入る。default ja-JP |
| local(true=自前 / false=翻訳) | → 移行 | kind | string enum へ変更(default transcription)。local=true→kind="transcription" / local=false→kind="translation" |
| error_message | → 移行 | error_message | — |
| created_at / updated_at | → 移行 | 同左 | — |
録画の文字起こしを一覧する
1 録画にぶら下がる文字起こし(複数言語 / 翻訳)を一覧します。既定ソートは created_at 昇順です。
クエリパラメータ
| パラメータ | 説明 |
|---|---|
| cursor任意 | ページングカーソル |
| limit任意 | 1〜100(default 20) |
エラー
| 条件 | レスポンス |
|---|---|
| 録画が不存在 or 他テナント | 404 NOT_FOUND |
curl …/api/v1/recordings/0190a1f0-…/transcriptions \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
{
"items": [
{ "id": "0190a210-…", "recording_id": "0190a1f0-…", "language": "ja-JP", "kind": "transcription", "status": "done" }
],
"next_cursor": null,
"has_more": false
}
Transcription を取得する
id(UUID 形式)で 1 件取得します。done の場合は result_url に短期署名 URL を含みます。
エラー
| 条件 | レスポンス |
|---|---|
| 不存在 or 他テナント | 404 NOT_FOUND |
curl …/api/v1/transcriptions/0190a210-… \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
{
"id": "0190a210-…",
"recording_id": "0190a1f0-…",
"room_id": "0190a1b3-…",
"provider": "openai",
"status": "done",
"language": "ja-JP",
"kind": "transcription",
"result_url": "https://.../signed-vtt…"
}
文字起こし本文を取得する
リソース表現の result_url が指す実体です。文字起こし本文は基盤内部(result_json・DB 行内格納)にありますが、払い出しは録画の download-url と同型の「短期署名 URL」パターンとし、保存先(DB / S3)に依存しない契約にします(将来 S3 化しても presigned への差し替えだけでテナント側は無変更)。
Bearer(API key)ではなく URL 埋め込みの署名で認可します。<track> / <video> タグなどヘッダを付与できない文脈から直接参照するためです(録画の S3 presigned と同じ理由)。expires は発行時点+3600 秒、sig は HMAC-SHA256(secret, "{path}?expires={expires}") の base64url(コア内部の対称鍵・参加 JWT / API key とは別系統)。
署名・期限を検証し、result_json から VTT を取得 / 生成(ElevenLabs / OpenAI は本文そのもの、Azure は result_json['vtt']、レガシー anyenv / rimo は都度変換)して 200 Content-Type: text/vtt; charset=utf-8 で返します。ベンダ生形式(result_json の生値)は返しません。本文は発言全文=個人情報のため、応答の事実を基盤側でログします。
エラー
| 条件 | レスポンス |
|---|---|
sig が不一致 | 403 INVALID_SIGNATURE |
expires 超過 | 410 URL_EXPIRED(取得 API を再実行して新しい result_url を得る) |
| Transcription が不存在(署名検証の通過後に判定) | 404 NOT_FOUND |
status が done 以外 | 409 TRANSCRIPTION_NOT_READY |
curl "…/api/v1/transcriptions/0190a210-…/result?expires=1752141600&sig=<base64url>"
WEBVTT 00:00:05.000 --> 00:00:07.500 <v speaker_0> お疲れ様です。
文字起こし/翻訳を起動する
録画に対し文字起こし / 翻訳をオンデマンド起動します(現行 operator 画面の transcribe / translate 後継)。録画完了時の自動文字起こしに加え、別言語・再実行・翻訳をオペ起点で要求する書き込み系です。Idempotency-Key に対応します。
ボディパラメータ
| パラメータ | 説明 |
|---|---|
| language任意 string(≤16) | 出力言語。BCP-47 コードで送る。許可値は ja-JP / en-US / ko-KR / zh-CN / zh-TW / vi-VN / th-TH の7コード(現行 AvailableLanguage 準拠)。未指定は Room の transcription_language。サービス固有表記(ja / zh 等)への変換はコア内部で行い API 契約には露出しない。⚠ zh-CN / zh-TW は OpenAI / ElevenLabs では同一値 zh になるため、区別が要る場合は provider: azure を指定 |
| kind任意 enum | transcription=自前文字起こし(default)/ translation=翻訳(旧 translate: boolean を enum 化) |
| provider任意 enum | エンジン上書き(eleven_labs / openai / azure / anyenv / rimo)。未指定は Room の transcription_service |
エラー
| 条件 | レスポンス |
|---|---|
録画 status が available 以外 | 409 RECORDING_NOT_AVAILABLE |
| language が上記7つの BCP-47 コード以外 / provider が不正 | 400 INVALID_REQUEST |
同一 (recording, language, kind) が処理中 | 409 TRANSCRIPTION_IN_PROGRESS |
成功時は 201 Created(Location: /api/v1/transcriptions/{transcription_id}・status: sending_request)を返します。完了は W6 transcription.completed で push 通知します。
curl -X POST …/api/v1/recordings/0190a1f0-…/transcriptions \ -H "Authorization: ApiKey key_live_a1b2.sk_…" \ -H "Idempotency-Key: 7c9e6679-…" \ -H "Content-Type: application/json" \ -d '{ "language": "en-US", "kind": "translation", "provider": "openai" }'
{
"id": "0190a211-…",
"recording_id": "0190a1f0-…",
"room_id": "0190a1b3-…",
"provider": "openai",
"status": "sending_request",
"language": "en-US",
"kind": "translation",
"result_url": null,
"created_at": "2026-07-01T06:20:00Z",
"updated_at": "2026-07-01T06:20:00Z"
}
W6 transcription.completed
文字起こしは録画完了(W3 available)より遅れて非同期に付くため、push 通知が必要です。minedia-www はこれを受けて Transcription を取得し、要約・集計を調査側で生成します。status: error / request_error は文字起こし失敗としてオペ / 調査側に通知します。
{
"id": "evt_0190a212-…",
"version": "1",
"type": "transcription.completed",
"occurred_at": "2026-07-01T06:14:30Z",
"data": {
"transcription_id": "0190a210-…",
"recording_id": "0190a1f0-…",
"room_id": "0190a1b3-…",
"session_id": "0190a1b2-…",
"session_external_ref": { "external_type": "Slot", "external_id": "123" },
"status": "done",
"language": "ja-JP",
"failure_reason": null
}
}
Handout
画質保持のため、メディアストリームではなく事前アップロード+ブラウザ直表示で提示する資料(image / video)です。割付ロジック(target_slot_type / project_handouts_slots)は調査ドメイン側に残置し、調査側が割付解決済みのファイルを Session に登録します。
「どの枠にどの提示物を出すか」の割付は Project の語彙に依存するため調査ドメイン側に残置します。基盤は割付解決済みのリストだけを受け取り、提示の実体(ファイル・順序・種別)のみを保持します。提示操作(show / hide)はルーム内ランタイムの能力(handout.present=moderator)であり、本 API の対象外です。
Handout オブジェクト
idProjectHandout / ExtemporaryProjectHandout(未指定時は null)external_type とペアで使用<img> / <video> でブラウザ直表示する(WebRTC ストリーム非経由)image / video0)id は RoomEvent payload(handout.show / handout.hide の handout_id)での参照キーです。file_url の有効期限はインタビュー時間(ends_at − 取得時刻)より長いことが制約です。
{
"id": "0190a1c0-…",
"session_id": "0190a1b2-…",
"external_type": "ProjectHandout",
"external_id": "12",
"file_name": "concept_A.png",
"file_url": "https://s3.../signed…",
"content_type": "image",
"sort": 0,
"created_at": "2026-06-10T09:00:00Z"
}
旧エンティティとの差分(project_handouts / extemporary_project_handouts → handouts)クリックで展開
| 旧フィールド(現行 minedia-www) | 種別 | 新フィールド(handouts) | 備考 |
|---|---|---|---|
| — | 🆕 新設 | id / tenant_id / uuid | 全コアテーブル共通の3 列をまとめた表記(1 つの識別子ではない)。id=内部 BIGINT 主キー(非公開・コア内 FK 用)/ tenant_id=テナントスコープ(PoC は 0 固定)/ uuid=公開識別子(RoomEvent payload・API での参照キー・API では id として露出) |
| project_handouts.id / extemporary_project_handouts.id(出所 PK) | → 移行 | external_type / external_id | 出所 PK を external_ref 化。例 {"ProjectHandout","12"}。FK ではない。調査側の再登録・同期時の重複排除キー |
| project_id / extemporary_slot_id + slot 割付の解決結果 | → 移行 | session_id | コア内部 FK → sessions.id。調査側が割付(Slot#all_project_handouts=all + slot 指定)を解決してから登録する |
| file(ProjectHandoutUploader) | → 移行 | file | 非公開バケット+短期署名 URL を踏襲(file_url として露出) |
| content_type | → 移行 | content_type | integer enum(1=image / 2=video)→ 文字列化(image / video) |
| sort | → 移行 | sort | 提示順(default 0) |
| project_handouts.target_slot_type(all/selected)/ project_handouts_slots(join) | ❌ 対象外 | — (調査に残置) | 割付は Project の語彙。コアは割付解決済みのリストだけを受け取る |
| created_at / updated_at | → 移行 | 同左 | — |
Handout を登録する
調査側が割付(target_slot_type / project_handouts_slots)を解決した後、提示する実体ファイルを Session に登録します。multipart/form-data でファイル本体を送信し、Idempotency-Key に対応します。
ボディパラメータ(multipart/form-data)
| パラメータ | 説明 |
|---|---|
| file必須 binary | 提示物ファイル本体 |
| content_type必須 enum | image / video |
| sort任意 integer ≥0 | 提示順(default 0) |
| external_type任意 string(≤64) | tenant 0: ProjectHandout / ExtemporaryProjectHandout |
| external_id任意 string(≤64) | テナント側 PK(整数も文字列化)。同一 external_ref の再登録は置換(上書き)=調査側の再同期を冪等にする |
エラー
| 条件 | レスポンス |
|---|---|
file / content_type 必須・external_ref ペア指定・sort は 0 以上の整数 | 400 INVALID_REQUEST |
| 拡張子ホワイトリスト外(image=jpg/jpeg/gif/png/bmp/tiff、video=mp4/mov。PDF・Office 不可) | 422 UNSUPPORTED_FILE_TYPE |
| サイズ上限超過(image 20MB / video 300MB・仮値) | 413 FILE_TOO_LARGE |
| 解放済み(cancelled)Session への登録 | 409 SESSION_RELEASED |
成功時は 201 Created と Location: /api/v1/handouts/{handout_id} を返します。
curl -X POST …/api/v1/sessions/0190a1b2-…/handouts \ -H "Authorization: ApiKey key_live_a1b2.sk_…" \ -H "Idempotency-Key: 7c9e6679-…" \ -F "file=@concept_A.png" \ -F "content_type=image" \ -F "sort=0" \ -F "external_type=ProjectHandout" \ -F "external_id=12"
{
"id": "0190a1c0-…",
"session_id": "0190a1b2-…",
"external_type": "ProjectHandout",
"external_id": "12",
"file_name": "concept_A.png",
"file_url": "https://s3.../signed…",
"content_type": "image",
"sort": 0,
"created_at": "2026-06-10T09:00:00Z"
}
Handout を一覧する
Session に登録済みの提示物を一覧します。既定ソートは sort 昇順です。
クエリパラメータ
| パラメータ | 説明 |
|---|---|
| cursor任意 | ページングカーソル |
| limit任意 | 1〜100(default 20) |
curl …/api/v1/sessions/0190a1b2-…/handouts \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
{
"items": [
{ "id": "0190a1c0-…", "session_id": "0190a1b2-…", "content_type": "image", "sort": 0 }
],
"next_cursor": null,
"has_more": false
}
Handout を更新する
メタデータ(sort 等)の部分更新と、任意のファイル差し替えを行います(multipart/form-data・全フィールド任意・指定したもののみ更新)。sort のみの並べ替えはファイル再送なしで行える点が、POST upsert(同 external_ref 全置換)との使い分けです。
ボディパラメータ(multipart/form-data)
| パラメータ | 説明 |
|---|---|
| sort任意 integer ≥0 | 提示順 |
| content_type任意 enum | image / video |
| file任意 binary | 差し替えファイル本体(指定時のみ置換。拡張子 / サイズは登録時と同一検証) |
エラー
| 条件 | レスポンス |
|---|---|
file 差し替え時、拡張子ホワイトリスト外 | 422 UNSUPPORTED_FILE_TYPE |
file 差し替え時、サイズ上限超過 | 413 FILE_TOO_LARGE |
sort が 0 以上の整数でない | 400 INVALID_REQUEST |
ルーム内で提示中(直近 handout.show 後に hide なし)の file 差し替え(sort 変更は可) | 409 HANDOUT_IN_USE |
| 解放済み(cancelled)Session の Handout | 409 SESSION_RELEASED |
成功時は 200 とリソース表現を返します。
curl -X PATCH …/api/v1/handouts/0190a1c0-… \ -H "Authorization: ApiKey key_live_a1b2.sk_…" \ -F "sort=2"
{
"id": "0190a1c0-…",
"session_id": "0190a1b2-…",
"file_name": "concept_A.png",
"content_type": "image",
"sort": 2,
"created_at": "2026-06-10T09:00:00Z"
}
Handout を削除する
登録済みの提示物を削除します。
エラー
| 条件 | レスポンス |
|---|---|
ルーム内で提示中(直近 handout.show 後に hide なし)は削除不可 | 409 HANDOUT_IN_USE |
成功時は 204 No Content。
curl -X DELETE …/api/v1/handouts/0190a1c0-… \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
204 No Content
ConnectionTest
現 BrowserTestHistory を汎用化した「被験者ブラウザ / 回線の品質計測」リソースです。接続テストは基盤で実施し(ダミー配信+購読負荷・計測 15 秒以上)、kind=connection_test の Room を 1 つ持ちます。合格判定(User.webrtc_test_status 相当)はテナント側の責務であり、基盤は計測の事実だけを返します。
診断ログ ConnectionTestEvent(entity §3.14)は基盤内部のサポート / ファネル分析用で、公開 API には露出しません。API が返すのは ConnectionTest の計測結果(summary / baseline / load)と録画参照までです。
ConnectionTest オブジェクト
created / completed / failed(🆕 新基盤で新設。現 BrowserTestHistory に status 列なし)bits_data)の有無で決まる(現 measurement_completed? = bits_data.present?)。false 時は summary / baseline / load は null。MEASUREMENT_MIN_SECONDS = 15 は計測下限でありフラグ条件には不使用User)。匿名テストは nullnullnull)null)measurement_completed=false 時は nullnulllegacy_record?)は null{ id, status } の配列。再生は §録画 download-url。なければ空配列livekit=LiveKit Cloud セッション画面)。提供されないエンジンは null[{ type, status, data }]。LiveKit ConnectionCheck の 8 チェック由来(signaling/webrtc/turn/reconnect/publish_audio/publish_video/protocol/region)。status=success/failed/skipped、data=ProtocolStats/RegionStats 等。failed の事由は checks[].status=failed で特定。OpenTok はスパース(空配列もあり)udp / tcp)CloudRegionCheck 由来。非 Cloud は null)null)summary / baseline / load はエンジン中立語彙(bitrate / packet loss / fps / jitter)で表現します。エンジン固有の生統計は基盤内部の raw_data に閉じ、API には出しません(OpenTok→LiveKit 交代でも列はそのまま使えます)。
{
"id": "0190a200-…",
"status": "completed",
"measurement_completed": true,
"external_type": "User",
"external_id": "999",
"dummy_stream_count": 5,
"hardware_concurrency": 8,
"connected_subscriber_count": 5,
"summary": {
"video_average_bitrate": 441000.0,
"video_median_bitrate": 477000.0,
"video_average_packets_loss_ratio": 0.0,
"video_median_packets_loss_ratio": 0.0,
"audio_average_bitrate": 31000.0,
"audio_median_bitrate": 32000.0,
"audio_average_packets_loss_ratio": 0.0,
"audio_median_packets_loss_ratio": 0.0
},
"baseline": {
"video_average_bitrate": 1850000.0,
"video_average_packets_loss_ratio": 0.001,
"frames_dropped_ratio": 0.0,
"fps": 29.8,
"audio_bitrate": 32000.0,
"audio_packets_loss_ratio": 0.0,
"audio_jitter": 0.004
},
"load": {
"video_average_bitrate": 1520000.0,
"video_average_packets_loss_ratio": 0.01,
"worst_video_bitrate": 980000.0,
"worst_video_packets_loss_ratio": 0.03,
"overall_frames_dropped_ratio": 0.02,
"worst_frames_dropped_ratio": 0.06,
"overall_fps": 27.0,
"worst_fps": 18.0,
"audio_average_bitrate": 31000.0,
"audio_average_packets_loss_ratio": 0.002,
"worst_audio_packets_loss_ratio": 0.01,
"audio_average_jitter": 0.006,
"worst_audio_jitter": 0.02
},
"recordings": [
{ "id": "0190a1f0-…", "status": "available" }
],
"provider_inspector_url": "https://cloud.livekit.io/projects/p_1uvc9dsiv14/sessions/RM_UNAo9Dh8Pq9t",
"completed_at": "2026-06-10T09:05:00Z",
"created_at": "2026-06-10T09:00:00Z"
}
旧エンティティとの差分(browser_test_histories → connection_tests)クリックで展開
| 旧フィールド(現行 minedia-www) | 種別 | 新フィールド(connection_tests) | 備考 |
|---|---|---|---|
| — | 🆕 新設 | id / tenant_id / uuid | 全コアテーブル共通の3 列をまとめた表記。id=内部 BIGINT 主キー(非公開)/ tenant_id=テナントスコープ(PoC は 0 固定)/ uuid=公開識別子(API では id として露出) |
has_one :ot_session | → 移行 | room_id | コア内部 FK → rooms.id(kind=connection_test の Room・unique。RtcSession は Room に統合・2026-06-29) |
| user_id(物理 FK) | → 移行 | external_type / external_id | 被験者 identity(tenant 委譲。例 {"User","999"})。FK ではない |
| — | 🆕 新設 | status | ライフサイクル created / completed / failed に明示化 |
| dummy_stream_count / hardware_concurrency / connected_subscriber_count | → 移行 | 同左 | 負荷シミュレーション構成(現状踏襲) |
| baseline_* 計測群(video bitrate / loss / fps / audio jitter 等) | → 移行 | 同左(API baseline) | ベースライン計測(現状踏襲) |
| load_* 計測群(average / worst の bitrate / loss / fps / jitter) | → 移行 | 同左(API load) | 負荷時計測(現状踏襲) |
| bits_data(JSON serialize) | → 移行 | raw_data | 汎用名へ(json)。生サンプル系列・エンジン固有の生統計はここに閉じる |
| 総合集計 8 列(video / audio_average / median_bitrate / *_packets_loss_ratio) | → 移行 | 同左(API summary) | 一覧描画に必須のためコアへ移植(旧「持ち込まない」判断を撤回)。管理画面 Web RTC 一覧の平均値 / 中央値 / パケットロス |
接続テストを開始する
被験者用の接続テストを作成し、基盤のテスト画面 URL(短命トークン付き)を払い出します。テナントは被験者ブラウザをこの URL に誘導してください。Idempotency-Key に対応します。
ボディパラメータ
| パラメータ | 説明 |
|---|---|
| external_type任意 string(≤64) | 被験者のテナント側参照(tenant 0: User)。external_id とペア指定。両方 null で匿名テスト |
| external_id任意 string(≤64) | 被験者のテナント側 PK。external_type とペア指定 |
エラー
| 条件 | レスポンス |
|---|---|
| external_ref を片方のみ指定 | 400 INVALID_REQUEST |
成功時は 201 Created と Location: /api/v1/connection-tests/{connection_test_id} を返します。
テスト URL の有効期限は 30 分固定です。
curl -X POST …/api/v1/connection-tests \ -H "Authorization: ApiKey key_live_a1b2.sk_…" \ -H "Idempotency-Key: 7c9e6679-…" \ -H "Content-Type: application/json" \ -d '{ "external_type": "User", "external_id": "999" }'
{
"id": "0190a200-…",
"test_url": "https://interview.minedia.com/connection-tests/0190a200-…?t=…",
"expires_at": "2026-06-10T09:30:00Z"
}
被験者ごとの履歴を取得する
minedia-www の operators/users/{id}「Web RTC」一覧のデータ源泉です。各項目が summary(平均値 / 中央値 / パケットロス)・recordings(動画)・provider_inspector_url(Inspector)・measurement_completed を内包するため、1 リクエストで一覧表を描画でき、行ごとの詳細呼び出し(N+1)は不要です。既定ソートは created_at 降順です。
クエリパラメータ
| パラメータ | 説明 |
|---|---|
| external_type必須 | 被験者単位の照会キー(external_id とペア指定) |
| external_id必須 | 被験者単位の照会キー(external_type とペア指定) |
| cursor任意 | ページングカーソル |
| limit任意 | 1〜100(default 20)。最新 N 件は limit で取得 |
external_ref を省略して作成した匿名テストは本一覧(被験者単位照会)には現れません。匿名テストは id 直接取得(次節)でのみ参照できます。
curl "…/api/v1/connection-tests?external_type=User&external_id=999" \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
{
"items": [
{
"id": "0190a200-…",
"status": "completed",
"measurement_completed": true,
"external_type": "User",
"external_id": "999",
"summary": {
"video_average_bitrate": 441000.0,
"video_median_bitrate": 477000.0,
"video_average_packets_loss_ratio": 0.0,
"audio_average_bitrate": 31000.0,
"audio_median_bitrate": 32000.0,
"audio_average_packets_loss_ratio": 0.0
},
"recordings": [ { "id": "0190a1f0-…", "status": "available" } ],
"provider_inspector_url": "https://cloud.livekit.io/projects/p_1uvc9dsiv14/sessions/RM_UNAo9Dh8Pq9t",
"created_at": "2026-06-10T09:00:00Z"
}
],
"next_cursor": null,
"has_more": false
}
結果を取得する
id(UUID 形式)で 1 件取得します。匿名テストもこの経路でのみ参照できます。計測結果(summary / baseline / load)を内包したリソース表現を返します。
エラー
| 条件 | レスポンス |
|---|---|
| 不存在 or 他テナント | 404 NOT_FOUND |
curl …/api/v1/connection-tests/0190a200-… \ -H "Authorization: ApiKey key_live_a1b2.sk_…"
{
"id": "0190a200-…",
"status": "completed",
"measurement_completed": true,
"external_type": "User",
"external_id": "999",
"dummy_stream_count": 5,
"connected_subscriber_count": 5,
"summary": { "video_average_bitrate": 441000.0, "video_median_bitrate": 477000.0 },
"baseline": { "video_average_bitrate": 1850000.0, "fps": 29.8 },
"load": { "video_average_bitrate": 1520000.0, "worst_fps": 18.0 },
"recordings": [ { "id": "0190a1f0-…", "status": "available" } ],
"provider_inspector_url": "https://cloud.livekit.io/projects/p_1uvc9dsiv14/sessions/RM_UNAo9Dh8Pq9t",
"completed_at": "2026-06-10T09:05:00Z",
"created_at": "2026-06-10T09:00:00Z"
}
W5 connection_test.completed
接続テストの計測完了時に送信されます。minedia-www はこれを受けて GET /connection-tests/{connection_test_id} で計測値を取得し、合格判定(User.webrtc_test_status)を実施します。
| data フィールド | 意味 |
|---|---|
connection_test_id | 完了した接続テストの公開 ID(結果取得に使用) |
external_ref | 被験者参照 { external_type, external_id }(匿名テストは双方 null) |
status | completed / failed |
{
"id": "evt_0190a210-…",
"version": "1",
"type": "connection_test.completed",
"occurred_at": "2026-06-10T09:05:00Z",
"data": {
"connection_test_id": "0190a200-…",
"external_ref": { "external_type": "User", "external_id": "999" },
"status": "completed"
}
}
署名鍵 (JWKS)
基盤自身が発行する JWT(EdDSA 署名)の検証用公開鍵セットです。kid で鍵をローテーションします。テナント署名鍵はここでは公開せず、鍵レジストリへのプッシュ登録で管理します。
このエンドポイントは共通エンベロープ(success / data)に包まれず、RFC 7517 標準形をそのまま返します。認証不要です。
公開鍵を取得する
認証不要。Model A では主に基盤内部の検証用ですが、テナント側での事前検証にも利用可能です。
{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"kid": "interview-2026-06",
"x": "...",
"use": "sig"
}
]
}
ヘルスチェック
基盤 API down 時のフォールバック判断を minedia-www が行うための死活確認です。
health 系は共通エンベロープに包まれず、素の liveness / readiness を返します。認証不要です。
liveness
認証不要。浅い liveness(プロセス生存のみ)を確認します。正常時 200、異常時 503 を返します。
{
"status": "ok"
}
readiness
DB / Redis / ストレージなど依存先まで含む準備状態を確認します。負荷分散・デプロイ制御用です。正常時 200、依存先異常時 503 を返します。
二重運用期に minedia-www は作成前に /health で基盤の可用性を見て、down ならフォールバック判断を行います(案件途中のエンジン切替は不可=作成時に確定)。
{
"status": "ready",
"checks": {
"db": "ok",
"redis": "ok"
}
}