重新翻譯 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/retranslate/{taskId} | 重新翻譯全文 |
| GET | /api/v1/sse/retranslate/summary/{taskId} | 重新翻譯摘要 |
| GET | /api/v1/sse/recordings/{taskId}/entries/{sid}/retranslate | 重新翻譯單一句子(編輯原文後使用) |
三支端點共通:內容無法翻譯時回報
llm_content_filtered,其severity是warning、context是translation, 與其餘翻譯失敗的error/sse不同。依severity過濾事件的客戶端請確認warning沒有被濾掉,否則這類失敗會整批消失。
GET /api/v1/sse/retranslate/{taskId}
功能說明
將指定任務的所有句子重新翻譯為目標語言。透過 SSE 串流逐條發送翻譯結果。
使用場景
- 切換顯示語言
- 更新翻譯內容
認證方式
Header:X-API-Key(詳見 認證機制)
請求參數
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
taskId | path | string | 是 | 錄音 ID(UUID) |
targetLang | query | string | 是 | 目標語言代碼(如 en-US) |
segmented | query | string | 否 | 帶 1 表示客戶端支援分段續翻,見下方 分段續翻(v1.18.0) |
fromSid | query | number | 否 | 只能和 segmented=1 一起使用:從這一句(含)開始翻;不帶就從頭開始(v1.18.0) |
expectedRevision | query | number | 否 | 逐字稿版本(≥ 1)。和目前的版本不符時不翻譯、不扣點,回 transcript_revision_conflict;兩種模式都可以帶(v1.18.0) |
分段續翻
很長的逐字稿一次重翻可能超過單次請求的處理時間。帶 segmented=1 時,每次請求只翻一段,再用 done 帶回的 nextSid 接著翻下一段。
沒帶 segmented | 帶 segmented=1 | |
|---|---|---|
| 處理方式 | 一次翻完整份,翻完才儲存、才扣點 | 從請求開始約 230 秒,時間到就在這一段停下;已翻的內容照常儲存 |
| 逐字稿太長 | 串流開始前回 HTTP 422 retranslate_segmentation_required,不扣點 | 不會收到這個錯誤,一律分段 |
done | 欄位不變 | 另帶 revision;這一段被截斷時再帶 truncated: true 與 nextSid |
| 計費 | 整份一次計費 | 每一段各自儲存、各自扣點,只算這一段翻的內容 |
續翻流程
- 送出
?targetLang=en-US&segmented=1。 - 收到
done:- 沒有
truncated:已經翻完。 - 有
truncated: true:以fromSid={nextSid}送出下一次請求(可同時帶expectedRevision={revision},確保這段期間逐字稿沒有被其他操作改動)。
- 沒有
- 重複到
done不再帶truncated為止。
注意:參數名稱是
fromSid與expectedRevision;寫成from_sid、expected_revision,或fromSid沒有搭配segmented=1,都會被拒絕(見下方「參數驗證失敗」),不會翻譯、不扣點。
請求範例
curl -N "https://vas-poc.vurbo.ai/api/v1/sse/retranslate/550e8400-e29b-41d4-a716-446655440000?targetLang=en-US" \
-H "X-API-Key: vas_aB3dE5fG7hI9jK1lM3nO5pQ7rS9tU1vW"
// 使用 fetch API(因 EventSource 不支援 Header)
async function retranslateSSE(taskId, targetLang, apiKey) {
const response = await fetch(
`https://vas-poc.vurbo.ai/api/v1/sse/retranslate/${taskId}?targetLang=${targetLang}`,
{
headers: {
'X-API-Key': apiKey
}
}
);
const reader = response.body.getReader();
// ... 處理 SSE 事件
}
事件序列
0. connected → 連線確認
1. translation → 逐條發送翻譯結果(成功句,重複 N 次)
error → 句子翻譯失敗(per-sid,與 translation 交錯出現)
error → 逐字稿儲存失敗、同時有其他寫入,或 expectedRevision 不符(storage_upload_failed/transcript_revision_conflict,流程中止、**不會**送出 done)
2. done → 翻譯完成(分段續翻時,也可能是這一段完成)
失敗句不發
translation,改發event: error帶sid+error_code,失敗事件的格式與 WebSocket 即時翻譯一致,前端可共用同一套錯誤處理。
事件格式
translation
{
"sid": 1,
"text": "Hello",
"is_final": true
}
| 欄位 | 類型 | 說明 |
|---|---|---|
sid | number | 句子 ID |
text | string | 翻譯結果 |
is_final | boolean | 是否為最終結果 |
error(per-sid 失敗)
當某句翻譯失敗時,不發 translation 而是發 error:
{
"error_code": "sse_translation_failed",
"severity": "error",
"message": "SSE translation failed",
"context": "sse",
"sid": 5,
"request_id": "req_abc123",
"timestamp": "2026-04-26T10:30:45.123Z",
"details": {
"translation_language": "ja-JP",
"original_error": "..."
}
}
| 欄位 | 類型 | 說明 |
|---|---|---|
error_code | string | 錯誤碼:sse_translation_failed 或 llm_content_filtered |
severity | string | 嚴重度。sse_translation_failed 為 error,llm_content_filtered 為 warning |
message | string | 人類可讀訊息 |
context | string | 錯誤上下文。sse_translation_failed 為 sse,llm_content_filtered 為 translation |
sid | int | 失敗的句子編號 |
details | object | 含 translation_language、original_error 等 debug 資訊 |
request_id | string | 本次請求識別碼,回報問題時附上可加速定位 |
timestamp | string | 事件發生時間(ISO 8601) |
兩種失敗要分開處理:
llm_content_filtered代表該句內容無法翻譯,重試不會改變結果,請修改原文後再試;sse_translation_failed代表翻譯當下未能完成,稍後重試通常可成功。 兩者的severity與context不同(見上表),若您依severity過濾事件,請確認warning沒有被濾掉。
失敗的句子會被儲存為翻譯錯誤記錄(見 history-playback),下次載入歷史時可看到失敗標記。 該記錄的值就是上述錯誤碼。失敗的語言不會寫入新的譯文:先前已有譯文的會維持舊譯文,先前沒有的則譯文中不會有這個語言。 因此不要只用「譯文欄位有沒有這個語言」判斷本次是否成功——那兩種情況分別會讓您誤判成成功與誤判成沒翻過。請一併檢查翻譯錯誤記錄。
done
{
"totalUpdated": 10,
"characters_billed": 12700,
"charged": "6.4",
"billed": true
}
| 欄位 | 類型 | 說明 |
|---|---|---|
totalUpdated | number | 更新的句子總數(不含失敗句) |
characters_billed | number | 本次計費依據的字元數 |
charged | string | 本次操作依費率計算的消耗點數。此值反映用量:吃到飽方案已涵蓋的用量,此欄位仍回報消耗量 |
billed | boolean | 本次是否產生消耗,恆為 true(未產生消耗時三個欄位皆不出現) |
revision | number | 只在帶 segmented=1 時出現:這一段結束後的逐字稿版本,可作為下一段的 expectedRevision(v1.18.0) |
truncated | boolean | 只在帶 segmented=1、而且這一段被截斷時出現,值一定是 true:還有句子沒翻(v1.18.0) |
nextSid | number | 與 truncated 一起出現:下一段請帶的 fromSid(v1.18.0) |
分段續翻時被截斷的 done 範例:
{
"totalUpdated": 640,
"characters_billed": 25600,
"charged": "12.8",
"billed": true,
"revision": 7,
"truncated": true,
"nextSid": 641
}
計費欄位:
characters_billed、charged、billed三欄僅在本次實際產生消耗時出現。 未產生消耗時(例如生成失敗)三欄皆不出現。判斷是否計費請以billed為準 (billed為true才是計費)——此判準適用所有帶計費欄位的端點,不需為個別端點寫例外。 代呼叫並向終端用戶計價的整合方,可直接採用charged而不必自行推算。
特有錯誤碼
| 錯誤碼 | HTTP 狀態碼 | 說明 | 處理建議 |
|---|---|---|---|
sse_translation_failed | 500 | 翻譯失敗(per-sid) | 失敗的單句仍透過 event: error 通知,整體流程不中斷 |
llm_content_filtered | 400 | 該句內容無法翻譯(per-sid) | 重試無效;請修改該句原文後再試。該句不計入 totalUpdated,也不產生消耗 |
recording_not_found | 404 | 找不到指定的錄音 | 確認 taskId 正確 |
recording_not_completed | 422 | 錄音尚未完成處理 | 等待錄音完成後重試 |
sse_transcript_not_found | 404 | 找不到逐字稿 | 錄音可能尚未處理完成 |
auth_insufficient_credit | 402 | 點數不足 | 這是串流開始前的真 HTTP 402 JSON 回應,不是 SSE 事件;儲值後再試 |
stt_quota_exceeded | 402 | 可用點數不足以支付本次預估消耗 | 同為串流開始前的 JSON 回應;儲值後再試 |
record_translation_not_allowed | 400 | 純錄音(record)類型不支援翻譯 | 同為串流開始前的 JSON 回應;改用 transcribe 類型的錄音 |
storage_upload_failed | 500 | 逐字稿儲存失敗 | 本次重翻全部作廢、不計費;稍後重試。收到此碼後不會再收到 done |
transcript_revision_conflict | 409 | 同一份逐字稿正有其他寫入在進行,或帶了 expectedRevision 但與目前的版本不符(details 帶 expected_revision 與 actual_revision) | 本次重翻全部作廢、不計費。版本不符時請重新載入逐字稿取得最新版本;其他情況稍後重試即可。收到此碼後不會再收到 done |
retranslate_segmentation_required | 422 | 沒帶 segmented=1,而逐字稿太長、一次請求翻不完(details.sentenceCount 為要翻的句數,details.maxSentences 為上限)(v1.18.0) | 這是串流開始前的 JSON 回應,不扣點;改帶 segmented=1 分段續翻 |
參數驗證失敗沒有錯誤碼:
targetLang缺漏或不是支援的語言,或segmented、fromSid、expectedRevision的值與用法不合規則時,回的是 HTTP 200 +event: error,data只有message一個欄位 —— 沒有error_code、severity、context、request_id、timestamp。請以message判讀,並注意這與本頁其他錯誤的格式不同。
前端範例
async function retranslate(taskId, targetLang, apiKey) {
const response = await fetch(
`https://vas-poc.vurbo.ai/api/v1/sse/retranslate/${taskId}?targetLang=${targetLang}`,
{
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 events = parseSSE(decoder.decode(value));
for (const event of events) {
if (event.type === 'translation') {
console.log(`句子 ${event.data.sid}: ${event.data.text}`);
} else if (event.type === 'done') {
console.log(`完成,共更新 ${event.data.totalUpdated} 句`);
}
}
}
}
GET /api/v1/sse/retranslate/summary/{taskId}
功能說明
將指定任務的摘要重新翻譯為目標語言。透過 SSE 串流逐段發送翻譯結果。
重新翻譯的結果不會儲存:已儲存的摘要與其語言都不變,重新載入歷史紀錄時仍是原本的摘要。要把摘要換成其他語言並保存,請使用 重新生成摘要 的儲存端點(POST,會計費)。
- 原文語言以摘要本身的語言為準。例如摘要已經重新生成為英文,就會當成英文來翻譯。
- 摘要較長時,串流常會一次送出一大段,兩則之間也可能停頓數秒,但每一則仍是累積全文。
- 處理時間與逾時的規則和 摘要翻譯 相同:一次請求上限約 230 秒,途中停頓超過 60 秒會送
error。 - 要翻譯服務端沒有的摘要內容(例如合併後或編輯過的摘要),請使用 摘要翻譯。
使用場景
- 切換摘要顯示語言
- 獲取不同語言的摘要
認證方式
Header:X-API-Key(詳見 認證機制)
請求參數
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
taskId | path | string | 是 | 錄音 ID(UUID) |
targetLang | query | string | 是 | 目標語言代碼(如 en-US) |
請求範例
curl -N "https://vas-poc.vurbo.ai/api/v1/sse/retranslate/summary/550e8400-e29b-41d4-a716-446655440000?targetLang=en-US" \
-H "X-API-Key: vas_aB3dE5fG7hI9jK1lM3nO5pQ7rS9tU1vW"
// 使用 fetch API(因 EventSource 不支援 Header)
async function retranslateSummarySSE(taskId, targetLang, apiKey) {
const response = await fetch(
`https://vas-poc.vurbo.ai/api/v1/sse/retranslate/summary/${taskId}?targetLang=${targetLang}`,
{
headers: {
'X-API-Key': apiKey
}
}
);
const reader = response.body.getReader();
// ... 處理 SSE 事件
}
事件序列
0. connected → 連線確認
1. summary_translation → 逐段發送摘要翻譯(重複 N 次)
error → 翻譯失敗(流程中止,**不會**送出 done)
2. done → 翻譯完成
事件格式
summary_translation
{
"text": "累積翻譯結果...",
"is_final": false
}
| 欄位 | 類型 | 說明 |
|---|---|---|
text | string | 累積的翻譯結果(串流式,逐漸增長) |
is_final | boolean | 是否為最終結果(最後一筆為 true) |
done
{
"totalUpdated": 1
}
| 欄位 | 類型 | 說明 |
|---|---|---|
totalUpdated | number | 固定為 1,表示已完成一份摘要的翻譯;不代表已儲存 |
truncated | boolean | 只在譯文不完整時出現,值一定是 true;譯文完整時不會有這個欄位。會出現的情況有兩種:翻譯達到處理時間或長度上限而被截斷,或譯文明顯少於原文(v1.17.0 新增) |
本端點不計費,
done不含characters_billed/charged/billed欄位。 僅重新翻譯既有摘要時不另行計費;全文重翻(本頁上一節)才計費。
特有錯誤碼
| 錯誤碼 | HTTP 狀態碼 | 說明 | 處理建議 |
|---|---|---|---|
sse_summary_not_found | 404 | 找不到摘要 | 該錄音沒有摘要 |
sse_summary_translation_failed | 500 | 摘要翻譯失敗。details.original_error 為 Translation timed out 時表示逾時(等待回應逾時、途中停頓超過 60 秒,或時間到時還沒有任何譯文) | 稍後重試 |
llm_content_filtered | 400 | 摘要內容無法翻譯 | 重試無效;請調整摘要內容後再試 |
recording_not_found | 404 | 找不到指定的錄音 | 確認 taskId 正確 |
recording_not_completed | 422 | 錄音尚未完成處理 | 等待錄音完成後重試 |
sse_transcript_not_found | 404 | 找不到逐字稿 | 錄音可能尚未處理完成 |
auth_insufficient_credit | 402 | 點數不足 | 這是串流開始前的真 HTTP 402 JSON 回應,不是 SSE 事件;儲值後再試 |
record_translation_not_allowed | 400 | 純錄音(record)類型不支援翻譯 | 同為串流開始前的 JSON 回應;改用 transcribe 類型的錄音 |
GET /api/v1/sse/recordings/{taskId}/entries/{sid}/retranslate
功能說明
重新翻譯單一句子。最常見場景:使用者透過 PATCH /api/v1/tasks/{id}/entries/{sid} 編輯原文後,呼叫此端點將該句的所有翻譯重做。
與全文重翻 (/retranslate/{taskId}) 的差異:
- 全文重翻:翻譯所有句子到指定語言(單一目標語言)
- 單句重翻:只翻譯一個句子,但可同時翻該句曾翻譯過或曾翻譯失敗過的所有語言
使用場景
- 使用者編輯 STT 原文後自動觸發
- 個別句子翻譯失敗的補救
認證方式
Header X-API-Key 或 Query api_key(兩者皆可)。瀏覽器原生 EventSource 不支援自訂 Header,這種情況用 Query 參數即可。詳見 認證機制。
請求參數
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
taskId | path | string | 是 | 錄音 ID(UUID) |
sid | path | number | 是 | 句子 ID(1-based) |
targetLang | query | string | 否 | 目標語言代碼。省略時會重翻該句所有「曾翻譯過或曾翻譯失敗過」的語言,也就是譯文與翻譯錯誤記錄兩者的聯集 |
expectedRevision | query | number | 否 | 樂觀鎖:當前 transcript revision;不符會回 transcript_revision_conflict |
api_key | query | string | 條件 | API Key。未帶 X-API-Key Header 時必填 |
請求範例
# 重翻所有已存在語言
curl -N "https://vas-poc.vurbo.ai/api/v1/sse/recordings/{taskId}/entries/5/retranslate?api_key=vas_xxx"
# 只重翻 en-US,並要求 revision 必須是 3
curl -N "https://vas-poc.vurbo.ai/api/v1/sse/recordings/{taskId}/entries/5/retranslate?targetLang=en-US&expectedRevision=3&api_key=vas_xxx"
事件序列
1. connected → 連線確認
2. progress → 開始翻譯某語言(每語言 1 次)
3. translated → 該語言翻譯完成(每語言 1 次)
或 error → 該語言翻譯失敗
4. done → 全部完成
事件格式
progress
{ "sid": 5, "lang": "en-US", "status": "translating" }
translated
{
"sid": 5,
"lang": "en-US",
"text": "Hello world",
"tokens_used": 25
}
error(單語言失敗)
錯誤碼與全文重翻同一組:內容無法翻譯時為 llm_content_filtered,其餘失敗為 sse_translation_failed。
下方範例只列關鍵欄位;實際事件的欄位與全文重翻的 error 完全相同(見上一節的欄位表)。
{
"error_code": "sse_translation_failed",
"sid": 5,
"details": { "translation_language": "ja-JP", "original_error": "..." }
}
done
{
"sid": 5,
"revision": 6,
"original_text_edited_at": "2026-05-06T10:30:00.000000Z",
"languages_translated": ["en-US"],
"languages_failed": ["ja-JP"]
}
| 欄位 | 類型 | 說明 |
|---|---|---|
sid | number | 句子 ID |
revision | number | 寫入後的新 revision(用於下次樂觀鎖) |
original_text_edited_at | string|null | 原文編輯時間(若該句被編輯過) |
languages_translated | array | 翻譯成功的語言代碼 |
languages_failed | array | 翻譯失敗的語言代碼 |
特有錯誤碼
| 錯誤碼 | HTTP 狀態碼 | 說明 | 處理建議 |
|---|---|---|---|
recording_not_found | 404 | 錄音不存在或不屬於該使用者 | 確認 taskId 正確 |
recording_not_completed | 422 | 錄音尚未完成處理 | 等待錄音完成後重試 |
entry_not_found | 404 | 找不到指定的句子 | 確認 sid 正確 |
entry_text_empty | 422 | 該句原文為空(只有空白字元也算) | 先透過 PATCH 編輯原文 |
sse_translation_failed | 500 | 某個目標語言翻譯失敗(per-lang) | 該語言會出現在 done 的 languages_failed,其餘語言不受影響;稍後重試 |
llm_content_filtered | 400 | 某個目標語言的內容無法翻譯(per-lang) | 該語言會出現在 done 的 languages_failed;重試無效,請修改該句原文後再試 |
auth_insufficient_credit | 402 | 點數不足 | 這是串流開始前的真 HTTP 402 JSON 回應,不是 SSE 事件;儲值後再試 |
record_translation_not_allowed | 400 | 純錄音(record)類型不支援翻譯 | 同為串流開始前的 JSON 回應;改用 transcribe 類型的錄音 |
transcript_revision_conflict | 409 | revision 不符,或同一份逐字稿正有其他寫入在進行 | 重新載入 transcript 取得最新 revision 後重試 |
storage_upload_failed | 500 | 逐字稿儲存失敗 | 本次寫入全部未生效;稍後重試。收到此碼後不會再收到 done |
版本:V1.24.1 最後更新:2026-09-28