SSE API

歷史紀錄 SSE

連線資訊

項目值
基礎路徑https://vas-poc.vurbo.ai/api/v1/sse
協定HTTP + Server-Sent Events (SSE)
資料格式text/event-stream
認證方式Header X-API-Key: {KEY},或 Query ?api_key={KEY}(兩者皆可,Query 優先)

注意:瀏覽器原生 EventSource API 不支援自訂 Header,需使用 fetch API 搭配 ReadableStream,或使用支援 Header 的 SSE 客戶端套件。


端點總覽

方法端點說明
GET/api/v1/sse/history/transcribe/{taskId}取得歷史對話紀錄

GET /api/v1/sse/history/transcribe/{taskId}

功能說明

載入指定任務的完整對話紀錄,包含所有句子和摘要。透過 SSE 串流逐條發送。

與 逐字稿下載 API(GET /api/v1/tasks/{taskId}/transcript/export)的差異:

  • 本端點:漸進式載入用途;以 event stream 逐句推送原始結構資料(JSON 片段),供前端 UI 漸進渲染。
  • 逐字稿下載:離線下載用途;一次回傳完整檔案(TXT / SRT / SBV / VTT / CSV),可直接交給字幕軟體或試算表開啟。

使用場景

  • 查看錄音詳情頁
  • 載入歷史逐字稿

認證方式

Header:X-API-Key(詳見 認證機制)

請求參數

參數位置類型必填說明
taskIdpathstring是錄音 ID(UUID)

請求範例

curl -N "https://vas-poc.vurbo.ai/api/v1/sse/history/transcribe/550e8400-e29b-41d4-a716-446655440000" \
  -H "X-API-Key: vas_aB3dE5fG7hI9jK1lM3nO5pQ7rS9tU1vW"
// 使用 fetch API(因 EventSource 不支援 Header)
async function connectSSE(taskId, apiKey) {
  const response = await fetch(
    `https://vas-poc.vurbo.ai/api/v1/sse/history/transcribe/${taskId}`,
    {
      headers: {
        'X-API-Key': apiKey
      }
    }
  );
  const reader = response.body.getReader();
  // ... 處理 SSE 事件
}

事件序列

1. connected        → 連線確認
2. init_metadata    → 發送任務元資料
3. init_sentence    → 逐條發送句子(重複 N 次)
4. init_summary     → 發送摘要
5. init_done        → 初始化完成

事件格式


connected

{
  "message": "歷史紀錄服務已連線 (taskId: xxx)"
}

init_metadata

{
  "task_id": "550e8400-e29b-41d4-a716-446655440000",
  "title": "會議記錄",
  "created_at": "2026-02-23T10:00:00Z",
  "type": "transcribe",
  "has_speaker_diarization": true,
  "transcription_languages": ["zh-TW"],
  "translation_languages": ["en-US"],
  "summary_template": "general",
  "summary_language": "zh-TW",
  "speaker_aliases": {"speaker_1": "王經理"}
}
欄位類型說明
task_idstring任務 ID(UUID)
titlestring任務標題
created_atstring建立時間(ISO 8601)
typestring錄音類型
has_speaker_diarizationboolean是否啟用說話者辨識
transcription_languagesarray轉錄語言陣列(BCP 47,如 ["zh-TW"]),最多 10 個;無資料時為 []
translation_languagesarray翻譯語言陣列(BCP 47,如 ["en-US", "ja-JP"]),最多 12 個;無資料時為 []
summary_templatestring|null摘要模板 slug(如 general、meeting),未指定時為 null
summary_languagestring|null摘要語言(BCP 47,如 zh-TW、en-US)。錄音時未指定摘要語言,一般為第一個轉錄語言;互譯模式有指定 active_language 時為該語言。沒有摘要的任務可能為 null。要判斷摘要本身的語言,請以 init_summary 的 summary_language 為準
speaker_aliasesobject「原始說話者 ID → 顯示名」映射;無別名時為 {}(空物件,非陣列)。前端用於 rename 前的撞名預檢(v1.3.12 新增)

init_sentence

{
  "sid": 1,
  "origin": "你好",
  "translations": {
    "en-US": "Hello"
  },
  "start_time": "00:05",
  "speaker_id": "speaker_1",
  "speaker_label": "王經理"
}

句子若有翻譯失敗,會額外帶 translation_errors 欄位(僅有失敗時出現),供前端區分「該語言未排程翻譯」(translations 缺 key)vs「翻過但失敗」(translation_errors 有 key);同一個語言可能同時有舊譯文與失敗記錄(重翻失敗時先前的譯文會保留),判讀時兩欄要一起看:

{
  "sid": 5,
  "origin": "敏感詞句子",
  "translations": {
    "en-US": "Sensitive sentence"
  },
  "translation_errors": {
    "ja-JP": "llm_content_filtered"
  },
  "start_time": "00:25",
  "speaker_id": "speaker_1",
  "speaker_label": "王經理"
}

句子若曾被使用者編輯過原文(透過 PATCH /api/v1/tasks/{id}/entries/{sid}),會額外帶 original_text_raw 與 original_text_edited_at(僅在編輯後出現):

{
  "sid": 7,
  "origin": "修正後的文字",
  "original_text_raw": "原本的 STT 輸出",
  "original_text_edited_at": "2026-05-06T10:30:00.000000Z",
  "translations": { "en-US": "Corrected text" },
  "start_time": "00:35",
  "speaker_id": "speaker_1",
  "speaker_label": "王經理"
}

多聲道錄音(recognition_mode: "multi_channel")的句子會額外帶 channel_id(僅多聲道錄音出現,單路錄音不帶此欄位):

{
  "sid": 9,
  "origin": "各位早安",
  "translations": {
    "en-US": "Good morning, everyone"
  },
  "start_time": "00:40",
  "speaker_id": "channel_1",
  "speaker_label": "王經理",
  "channel_id": 1
}
欄位類型說明
sidnumber句子 ID
originstring原文內容(被編輯過時為使用者修正後版本)
translationsobject|null翻譯文字 map({"語言代碼": "翻譯文字"}),無翻譯時為 null
translation_errorsobject可選。翻譯失敗錯誤碼 map({"語言代碼": "error_code"}),無失敗時不出現此欄位
original_text_rawstring可選。STT 原始輸出文字。僅在被使用者編輯過時出現,前端可用以顯示「已編輯」標記、提供「還原原文」功能
original_text_edited_atstring可選。原文最近編輯時間(ISO 8601)。與 original_text_raw 同步出現
start_timestring開始時間(mm:ss)
speaker_idstring|null說話者 ID(如 Guest-1;同一人跨連線恢復不保證同號,見說話者管理)。提供給 PATCH /speakers/reassign 作為 target_speaker_id 來源(v1.5.3 翻轉:原為顯示名)
speaker_labelstring|null顯示標籤(套 speaker_aliases 後的人類可讀名稱,如 王經理)。無 alias 時等同 speaker_id(v1.5.3 新增取代原 speaker_id 顯示語意)
channel_idnumber可選。本句的來源聲道編號(start 時 channels[].channel_id 的值)。僅多聲道錄音出現,單路錄音不帶此欄位(V1.10.0 新增)

前端 detection:以欄位存在性判斷句子是否被編輯過('original_text_raw' in data 或 data.original_text_raw !== undefined),不要比對 origin === original_text_raw — 使用者可能編輯後又改回相同字串,那種情況下文字相等但仍應顯示「已編輯」標記。

v1.5.3 命名翻轉:speaker_id 從顯示名翻轉為原始 ID;新增 speaker_label 為顯示標籤。語者編輯(reassign / merge)一律以 speaker_id 為定位 key。詳見 V1.5.3 changelog。

多聲道錄音(V1.10.0):speaker_id 為 channel_{N} 格式(N=聲道編號),語者身分由實體聲道決定、非 AI 推斷;speaker_label 為該路設定的 speaker_name(或經 rename_speaker 修改後的顯示名),無別名時等同 speaker_id。channel_id 與 speaker_id 資訊重疊是刻意設計 —— channel_id 是不可變的來源聲道紀錄,供稽核時分別追溯。另注意多聲道下句子依時間排序發送,sid 不保證遞增,客戶端請勿以 sid 大小假設先後順序。


init_summary

除摘要文字 text 外,含 mode-aware metadata(mode / template / plain_text / prompt_snapshot),讓客戶端追溯該份摘要對應的 mode + effective slug + 客戶 prompt 內容(custom mode)。

v1.5.5 新增 fallback_level / dropped_segments:當該份摘要實際走過 LLM 服務內容過濾的自動降級(中性模式或段落省略模式)時才出現,供歷史回放時查核與 UI 提示。

範例(標準模式直接成功,無 fallback):

{
  "text": "摘要內容...",
  "mode": "custom",
  "template": "acme-meeting-v2",
  "plain_text": true,
  "summary_language": "zh-TW",
  "prompt_snapshot": "請強調 KPI"
}

範例(段落省略模式觸發,省略 2 段逐字稿後產出):

{
  "text": "摘要內容(已省略 2 段)...",
  "mode": "custom",
  "template": "acme-meeting-v2",
  "plain_text": true,
  "summary_language": "zh-TW",
  "prompt_snapshot": "請強調 KPI",
  "fallback_level": 3,
  "dropped_segments": [3, 7]
}
欄位類型說明
textstring摘要文字
modestring | null"builtin" / "custom" / null(未生成摘要時為 null)
templatestring | nulleffective slug — builtin → 內建模板 slug;custom → 客戶 slug
plain_textboolean是否為純文字輸出
summary_languagestring | null這份摘要的語言(BCP 47,如 zh-TW)。一定會出現。錄音時未指定摘要語言,一般為第一個轉錄語言;互譯模式有指定 active_language 時為該語言。沒有摘要(text 為空字串)時為 null(v1.16.5 新增)
prompt_snapshotstring | null僅 custom mode 有值,為客戶原樣傳入的 prompt 內容(重建依據)
fallback_levelint (omit)僅 fallback 觸發時出現(2 或 3)。2=中性模式(改用中性指令重新生成);3=段落省略模式(省略觸發段落後重新生成)。標準模式直接成功則 omit
dropped_segmentsint[] (omit)僅 fallback_level=3 時出現,被剝除的逐字稿段 indices(原序整數陣列)

summary_language 與 init_metadata 的同名欄位:判斷摘要本身的語言,請以本事件的 summary_language 為準。沒有摘要時,init_metadata.summary_language 仍可能帶有錄音時的語言設定,而本欄為 null,兩者不同屬正常。

fallback_level / dropped_segments 與 prompt_snapshot 互補:前者記錄實際執行路徑(是否走 fallback),後者記錄客戶意圖(原 prompt 內容)。即使 fallback 觸發、客戶 prompt 未實際使用,prompt_snapshot 仍保留原文作為審計依據。詳見 V1.5.5 changelog – LLM 服務內容過濾自動降級。


init_done

{
  "totalSentences": 10
}
欄位類型說明
totalSentencesnumber總句子數

邊界情境:無語音內容(V1.3.7)

若任務全程靜音、音量過小、雜訊過多,或辨識語言與實際音檔不符,導致語音辨識引擎未辨識出任何句子,此端點仍以正常事件序列完成(不是 sse_transcript_not_found 錯誤):

  • init_metadata 正常發送
  • init_sentence 發送 0 次(無句子)
  • init_summary 的 text 為空字串 "",summary_language 為 null
  • init_done 的 totalSentences 為 0

此行為適用於 即時錄音(WebSocket 錄音結束)與 檔案匯入(離線處理完成)兩種來源,已與 V1.3.5 匯入流程的「零辨識結果」合法化行為對齊。客戶端應透過 totalSentences === 0 判斷是否顯示「無語音內容」空狀態,而非將其視為錯誤分支。詳見 音檔匯入指南 – 音檔無法辨識時的行為。

特有錯誤碼

錯誤碼HTTP 狀態碼說明處理建議
recording_not_found404找不到錄音確認 taskId 正確
sse_transcript_not_found404找不到逐字稿指定 taskId 的逐字稿不存在或讀取失敗。整場沒有收到任何音訊的即時錄音(task_complete 帶 noAudio: true)沒有逐字稿,會收到此錯誤;有錄到音訊、只是沒有語音內容的錄音不會觸發此錯誤

前端範例

async function loadHistory(taskId, apiKey) {
  const response = await fetch(
    `https://vas-poc.vurbo.ai/api/v1/sse/history/transcribe/${taskId}`,
    {
      headers: {
        'X-API-Key': apiKey
      }
    }
  );

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const text = decoder.decode(value);
    // 解析 SSE 格式:event: xxx\ndata: {...}\n\n
    const events = parseSSE(text);

    for (const event of events) {
      if (event.type === 'init_metadata') {
        console.log('任務資訊:', event.data.title);
      } else if (event.type === 'init_sentence') {
        console.log(`[${event.data.start_time}] ${event.data.origin}`);
        if (event.data.translations) {
          console.log(`翻譯:`, event.data.translations);
        }
      } else if (event.type === 'init_summary') {
        console.log('摘要:', event.data.text);
      } else if (event.type === 'init_done') {
        console.log(`載入完成,共 ${event.data.totalSentences} 句`);
      }
    }
  }
}

版本:V1.24.1 最後更新:2026-10-01

Copyright © 2026