SSE API

History

連線資訊

項目
基礎路徑https://vas-poc.vurbo.ai/api/v1/sse
協定HTTP + Server-Sent Events (SSE)
資料格式text/event-stream
認證方式Header X-API-Key: {KEY}

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


端點總覽

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

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

功能說明

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

逐字稿下載 APIGET /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": "歷史紀錄服務已連線 (recordingId: 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|null轉錄語言陣列(BCP 47,如 ["zh-TW"]),最多 2 個
translation_languagesarray|null翻譯語言陣列(BCP 47,如 ["en-US", "ja-JP"]),最多 8 個
summary_templatestring|null摘要模板 slug(如 generalmeeting),未指定時為 null
summary_languagestring|null摘要輸出語言(BCP 47,如 zh-TWen-US),未指定時為 null
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": "llm_content_filtered"
  },
  "start_time": "00:25",
  "speaker_id": "speaker_1",
  "speaker_label": "王經理"
}

句子若曾被使用者編輯過原文(透過 PATCH /api/v1/recordings/{id}/entries/{sid}),會額外帶 original_text_raworiginal_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": "王經理"
}
欄位類型說明
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(不可變、永遠穩定,如 speaker_1)。提供給 PATCH /speakers/reassign 作為 target_speaker_id 來源(v1.5.3 翻轉:原為顯示名)
speaker_labelstring|null顯示標籤(套 speaker_aliases 後的人類可讀名稱,如 王經理)。無 alias 時等同 speaker_id(v1.5.3 新增取代原 speaker_id 顯示語意)

前端 detection:以欄位存在性判斷句子是否被編輯過('original_text_raw' in datadata.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


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 服務內容過濾 fallback chain(L2 中性 prompt 或 L3 分段丟段)時才出現,供歷史回放時 audit 與 UI 提示。

範例(L1 直接成功,無 fallback):

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

範例(L3 觸發,逐字稿被剝除 2 段後產出):

{
  "text": "摘要內容(已省略 2 段)...",
  "mode": "custom",
  "template": "acme-meeting-v2",
  "plain_text": true,
  "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是否為純文字輸出
prompt_snapshotstring | null僅 custom mode 有值,為客戶原樣傳入的 prompt 內容(重建依據)
fallback_levelint (omit)僅 fallback 觸發時出現23)。2=L2 中性 prompt;3=L3 分段丟段。L1 直接成功則 omit
dropped_segmentsint (omit)僅 fallback_level=3 時出現,被剝除的逐字稿段 indices(原序整數陣列)

fallback_level / dropped_segmentsprompt_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_summarytext 為空字串 ""
  • init_donetotalSentences0

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

特有錯誤碼

錯誤碼HTTP 狀態碼說明處理建議
recording_not_found404找不到錄音確認 taskId 正確
sse_transcript_not_found404找不到逐字稿 blob指定 taskId 的逐字稿檔案不存在或存取失敗(正常流程下不會出現;V1.3.7 後即時錄音靜音亦不會觸發此錯誤)

前端範例

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.5.7 最後更新:2026-05-20

Copyright © 2026