Retranslation SSE
Connection Information
| Item | Value |
|---|---|
| Base path | https://vas-poc.vurbo.ai/api/v1/sse |
| Protocol | HTTP + Server-Sent Events (SSE) |
| Data format | text/event-stream |
| Authentication | Header X-API-Key: {KEY}, or query ?api_key={KEY} (either works; query takes precedence) |
Note: The browser's native EventSource API does not support custom headers. Use the fetch API together with ReadableStream, or use an SSE client library that supports headers.
Endpoint Overview
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/sse/retranslate/{taskId} | Retranslate full transcript |
| GET | /api/v1/sse/retranslate/summary/{taskId} | Retranslate summary |
| GET | /api/v1/sse/recordings/{taskId}/entries/{sid}/retranslate | Retranslate a single sentence (use after editing the original text) |
Common to all three endpoints: when content cannot be translated,
llm_content_filteredis reported withseveritywarningandcontexttranslation, unlike theerror/sseused for other translation failures. If your client filters events byseverity, make surewarningis not filtered out, or these failures disappear entirely.
GET /api/v1/sse/retranslate/{taskId}
Description
Retranslates all sentences of the specified task into the target language. Translation results are streamed one by one over SSE.
Use Cases
- Switching the display language
- Updating translated content
Authentication
Header: X-API-Key (see Authentication)
Request Parameters
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
taskId | path | string | Yes | Recording ID (UUID) |
targetLang | query | string | Yes | Target language code (e.g., en-US) |
segmented | query | string | No | 1 declares that the client supports segmented retranslation; see Segmented Retranslation below (v1.18.0) |
fromSid | query | number | No | Only together with segmented=1: start from this sentence (inclusive); when omitted, start from the beginning (v1.18.0) |
expectedRevision | query | number | No | Transcript revision (≥ 1). If it does not match the current revision, nothing is translated or charged and transcript_revision_conflict is returned; allowed in both modes (v1.18.0) |
Segmented Retranslation
Retranslating a very long transcript in one go can exceed the processing time of a single request. With segmented=1, each request translates one segment, and you continue with the nextSid returned in done.
Without segmented | With segmented=1 | |
|---|---|---|
| Processing | Translates the whole transcript at once; saved and charged only when finished | Stops at the current segment about 230 seconds after the request starts; what was translated is saved as usual |
| Transcript too long | HTTP 422 retranslate_segmentation_required before the stream starts; no charge | Never returned; always segmented |
done | Fields unchanged | Also carries revision; when the segment was cut short, also truncated: true and nextSid |
| Billing | Charged once for the whole transcript | Each segment is saved and charged separately, only for what that segment translated |
Continuation flow
- Send
?targetLang=en-US&segmented=1. - When
donearrives:- No
truncated: translation is finished. truncated: true: send the next request withfromSid={nextSid}(you can also sendexpectedRevision={revision}to make sure the transcript was not changed by anything else in the meantime).
- No
- Repeat until
doneno longer carriestruncated.
Note: The parameter names are
fromSidandexpectedRevision. Sendingfrom_sidorexpected_revision, orfromSidwithoutsegmented=1, is rejected (see "Parameter validation failures" below); nothing is translated or charged.
Request Example
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"
// Use the fetch API (because EventSource does not support headers)
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();
// ... handle SSE events
}
Event Sequence
0. connected → Connection confirmed
1. translation → sends translation results one by one (successful sentences, repeated N times)
error → a sentence failed to translate (per-sid, interleaved with translation)
error → failed to save the transcript, another write is in progress, or expectedRevision does not match (storage_upload_failed / transcript_revision_conflict; the flow stops and done is **not** sent)
2. done → translation complete (in segmented mode, possibly just this segment)
Failed sentences do not emit
translation; instead they emitevent: errorcarryingsid+error_code. The failure event format matches real-time translation over WebSocket, so the frontend can share one error handler.
Event Format
translation
{
"sid": 1,
"text": "Hello",
"is_final": true
}
| Field | Type | Description |
|---|---|---|
sid | number | Sentence ID |
text | string | Translation result |
is_final | boolean | Whether this is the final result |
error (per-sid failure)
When a sentence fails to translate, translation is not emitted; error is emitted instead:
{
"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": "..."
}
}
| Field | Type | Description |
|---|---|---|
error_code | string | Error code: sse_translation_failed or llm_content_filtered |
severity | string | Severity. error for sse_translation_failed, warning for llm_content_filtered |
message | string | Human-readable message |
context | string | Error context. sse for sse_translation_failed, translation for llm_content_filtered |
sid | int | Number of the failed sentence |
details | object | Debug information including translation_language, original_error, etc. |
request_id | string | Identifier for this request; including it when reporting a problem speeds up diagnosis |
timestamp | string | When the event occurred (ISO 8601) |
Handle the two failures differently:
llm_content_filteredmeans the sentence content cannot be translated and retrying will not change the result — revise the original text and try again.sse_translation_failedmeans the translation did not complete this time; retrying later usually succeeds. The two carry differentseverityandcontextvalues (see the table above). If you filter events byseverity, make surewarningis not filtered out.
Failed sentences are stored as translation error records (see history-playback), so the failure markers appear the next time the history is loaded. The stored value is the error code above. A failed language is not written with a new translation: if it already had one, the previous translation is kept; if it did not, the language is absent from the translations. So do not decide whether this run succeeded by checking only whether the language key is present — those two cases would read as success and as never-translated respectively. Check the translation error records as well.
done
{
"totalUpdated": 10,
"characters_billed": 12700,
"charged": "6.4",
"billed": true
}
| Field | Type | Description |
|---|---|---|
totalUpdated | number | Total number of sentences updated (excluding failed sentences) |
characters_billed | number | Character count used as the billing basis for this request |
charged | string | Points consumed by this operation, calculated from the rate. This value reflects usage: usage already covered by an unlimited plan is still reported here |
billed | boolean | Whether this request incurred consumption; always true (all three fields are absent when nothing was consumed) |
revision | number | Only with segmented=1: the transcript revision after this segment; can be used as expectedRevision for the next segment (v1.18.0) |
truncated | boolean | Only with segmented=1 and only when this segment was cut short; always true: some sentences are not translated yet (v1.18.0) |
nextSid | number | Appears together with truncated: the fromSid to send for the next segment (v1.18.0) |
Example of a done cut short in segmented mode:
{
"totalUpdated": 640,
"characters_billed": 25600,
"charged": "12.8",
"billed": true,
"revision": 7,
"truncated": true,
"nextSid": 641
}
Billing fields:
characters_billed,charged, andbilledappear only when the request actually incurred consumption. When nothing was consumed (for example, when generation fails), all three are absent. Always usebilledto determine whether a request was billed (billed only whenbilledistrue) — that criterion applies to every endpoint that carries billing fields, with no per-endpoint exceptions. Integrators that call this API on behalf of end users and bill them separately can usechargeddirectly instead of deriving it.
Specific Error Codes
| Error Code | HTTP Status | Description | Recommended Action |
|---|---|---|---|
sse_translation_failed | 500 | Translation failed (per-sid) | The failed sentence is still reported via event: error; the overall flow is not interrupted |
llm_content_filtered | 400 | The sentence content cannot be translated (per-sid) | Retrying will not help; revise the original text and try again. The sentence is excluded from totalUpdated and incurs no consumption |
recording_not_found | 404 | Recording not found | Verify that taskId is correct |
recording_not_completed | 422 | The recording has not finished processing | Wait for it to complete and retry |
sse_transcript_not_found | 404 | Transcript not found | The recording may not have finished processing |
auth_insufficient_credit | 402 | Insufficient credit | This is a real HTTP 402 JSON response returned before the stream starts, not an SSE event; top up and retry |
stt_quota_exceeded | 402 | Available credit does not cover the estimated cost of this request | Also a JSON response returned before the stream starts; top up and retry |
record_translation_not_allowed | 400 | Recording-only (record) tasks do not support translation | Also a JSON response returned before the stream starts; use a transcribe recording instead |
storage_upload_failed | 500 | Failed to save the transcript | The whole run is discarded and not billed; try again later. After this code you will not receive done |
transcript_revision_conflict | 409 | Another write to the same transcript is in progress, or expectedRevision was sent and does not match the current revision (details carries expected_revision and actual_revision) | The whole run is discarded and not billed. On a revision mismatch, reload the transcript to get the latest revision; otherwise simply try again later. After this code you will not receive done |
retranslate_segmentation_required | 422 | segmented=1 was not sent and the transcript is too long to translate in one request (details.sentenceCount is the number of sentences to translate, details.maxSentences is the limit) (v1.18.0) | This is a JSON response before the stream starts, with no charge; send segmented=1 to retranslate in segments |
Parameter validation failures carry no error code: when
targetLangis missing or is not a supported language, or whensegmented,fromSid, orexpectedRevisionhas an invalid value or usage, the response is HTTP 200 withevent: error, anddatacontains onlymessage— noerror_code,severity,context,request_idortimestamp. Readmessagedirectly, and note that this differs from the other errors on this page.
Frontend Example
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(`Sentence ${event.data.sid}: ${event.data.text}`);
} else if (event.type === 'done') {
console.log(`Done, ${event.data.totalUpdated} sentences updated`);
}
}
}
}
GET /api/v1/sse/retranslate/summary/{taskId}
Description
Retranslates the summary of the specified task into the target language. Translation results are streamed segment by segment over SSE.
The retranslated result is not saved: the saved summary and its language are unchanged, and reloading the history still returns the original summary. To switch the summary to another language and keep it, use the save endpoint of Regenerate Summary (POST, billed).
- The source language is the language of the summary itself. For example, a summary that has been regenerated in English is translated as English.
- For a long summary, the stream often delivers a large block at once, with a pause of a few seconds between events, but each event is still the cumulative full text.
- Processing time and timeouts follow the same rules as Summary Translation: a request has a limit of about 230 seconds, and an
erroris sent if translation pauses for more than 60 seconds. - To translate summary content the server does not have (for example a merged or edited summary), use Summary Translation.
Use Cases
- Switching the summary display language
- Obtaining the summary in a different language
Authentication
Header: X-API-Key (see Authentication)
Request Parameters
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
taskId | path | string | Yes | Recording ID (UUID) |
targetLang | query | string | Yes | Target language code (e.g., en-US) |
Request Example
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"
// Use the fetch API (because EventSource does not support headers)
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();
// ... handle SSE events
}
Event Sequence
0. connected → Connection confirmed
1. summary_translation → sends summary translation segment by segment (repeated N times)
error → translation failed (the flow stops; done is **not** sent)
2. done → translation complete
Event Format
summary_translation
{
"text": "Accumulated translation result...",
"is_final": false
}
| Field | Type | Description |
|---|---|---|
text | string | Accumulated translation result (streamed, grows gradually) |
is_final | boolean | Whether this is the final result (the last item is true) |
done
{
"totalUpdated": 1
}
| Field | Type | Description |
|---|---|---|
totalUpdated | number | Always 1, meaning one summary has been translated; it does not mean anything was saved |
truncated | boolean | Present only when the translation is incomplete (the value is always true); absent when the translation is complete. It appears when the translation hit the processing-time or length limit and was cut off, or when the translation is clearly shorter than the source (added in v1.17.0) |
This endpoint is not billed. Its
doneevent does not include thecharacters_billed/charged/billedfields. Re-translating an existing summary incurs no additional charge; only full-text retranslation (previous section) is billed.
Specific Error Codes
| Error Code | HTTP Status | Description | Recommended Action |
|---|---|---|---|
sse_summary_not_found | 404 | Summary not found | This recording has no summary |
sse_summary_translation_failed | 500 | Summary translation failed. When details.original_error is Translation timed out, the request timed out (waiting for a response timed out, translation paused for more than 60 seconds, or the time limit was reached before any translation arrived) | Try again later |
llm_content_filtered | 400 | The summary content cannot be translated | Retrying will not help; revise the summary and try again |
recording_not_found | 404 | Recording not found | Verify that taskId is correct |
recording_not_completed | 422 | The recording has not finished processing | Wait for it to complete and retry |
sse_transcript_not_found | 404 | Transcript not found | The recording may not have finished processing |
auth_insufficient_credit | 402 | Insufficient credit | This is a real HTTP 402 JSON response returned before the stream starts, not an SSE event; top up and retry |
record_translation_not_allowed | 400 | Recording-only (record) tasks do not support translation | Also a JSON response returned before the stream starts; use a transcribe recording instead |
GET /api/v1/sse/recordings/{taskId}/entries/{sid}/retranslate
Description
Retranslates a single sentence. The most common scenario: after a user edits the original text via PATCH /api/v1/tasks/{id}/entries/{sid}, this endpoint is called to redo all translations of that sentence.
Differences from full-transcript retranslation (/retranslate/{taskId}):
- Full-transcript retranslation: translates all sentences into a specified language (a single target language)
- Single-sentence retranslation: translates only one sentence, but can retry every language it has been translated into or failed to translate into at once
Use Cases
- Automatically triggered after a user edits the STT original text
- Recovery for individual sentences that failed to translate
Authentication
Header X-API-Key or query api_key — both work. The browser's native EventSource cannot send custom headers, so use the query parameter in that case. See Authentication.
Request Parameters
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
taskId | path | string | Yes | Recording ID (UUID) |
sid | path | number | Yes | Sentence ID (1-based) |
targetLang | query | string | No | Target language code. When omitted, every language that sentence has either been translated into or failed to translate into is retried, that is the union of the translations and the translation error records |
expectedRevision | query | number | No | Optimistic lock: the current transcript revision; a mismatch returns transcript_revision_conflict |
api_key | query | string | Conditional | API Key. Required when the X-API-Key header is not sent |
Request Example
# Retranslate all existing languages
curl -N "https://vas-poc.vurbo.ai/api/v1/sse/recordings/{taskId}/entries/5/retranslate?api_key=vas_xxx"
# Retranslate only en-US, and require the revision to be 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"
Event Sequence
1. connected → connection confirmed
2. progress → started translating a language (once per language)
3. translated → that language's translation completed (once per language)
or error → that language's translation failed
4. done → all complete
Event Format
progress
{ "sid": 5, "lang": "en-US", "status": "translating" }
translated
{
"sid": 5,
"lang": "en-US",
"text": "Hello world",
"tokens_used": 25
}
error (single-language failure)
The error codes are the same set as full-text retranslation: llm_content_filtered when the content cannot be translated, and sse_translation_failed otherwise.
The example below shows only the key fields; the actual event carries the same fields as the error event of full-text retranslation (see the field table in that section).
{
"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"]
}
| Field | Type | Description |
|---|---|---|
sid | number | Sentence ID |
revision | number | The new revision after the write (used for the next optimistic lock) |
original_text_edited_at | string|null | Time the original text was edited (if this sentence has been edited) |
languages_translated | array | Language codes that were translated successfully |
languages_failed | array | Language codes that failed to translate |
Specific Error Codes
| Error Code | HTTP Status | Description | Recommended Action |
|---|---|---|---|
recording_not_found | 404 | Recording does not exist or does not belong to this user | Verify that taskId is correct |
recording_not_completed | 422 | Recording processing is not yet complete | Wait for the recording to complete, then try again |
entry_not_found | 404 | The specified sentence was not found | Verify that sid is correct |
entry_text_empty | 422 | The original text of this sentence is empty (whitespace-only counts as empty) | Edit the original text via PATCH first |
sse_translation_failed | 500 | A target language failed to translate (per-lang) | That language appears in languages_failed in done; other languages are unaffected. Try again later |
llm_content_filtered | 400 | A target language's content cannot be translated (per-lang) | That language appears in languages_failed in done; retrying will not help, revise the original text |
auth_insufficient_credit | 402 | Insufficient credit | This is a real HTTP 402 JSON response returned before the stream starts, not an SSE event; top up and retry |
record_translation_not_allowed | 400 | Recording-only (record) tasks do not support translation | Also a JSON response returned before the stream starts; use a transcribe recording instead |
transcript_revision_conflict | 409 | Revision mismatch, or another write to the same transcript is in progress | Reload the transcript to get the latest revision, then try again |
storage_upload_failed | 500 | Failed to save the transcript | Nothing from this run was written; try again later. After this code you will not receive done |
Version: V1.24.1 Last Updated: 2026-09-28