SSE API

Retranslation SSE

Connection Information

ItemValue
Base pathhttps://vas-poc.vurbo.ai/api/v1/sse
ProtocolHTTP + Server-Sent Events (SSE)
Data formattext/event-stream
AuthenticationHeader 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

MethodEndpointDescription
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}/retranslateRetranslate a single sentence (use after editing the original text)

Common to all three endpoints: when content cannot be translated, llm_content_filtered is reported with severity warning and context translation, unlike the error / sse used for other translation failures. If your client filters events by severity, make sure warning is 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

ParameterLocationTypeRequiredDescription
taskIdpathstringYesRecording ID (UUID)
targetLangquerystringYesTarget language code (e.g., en-US)
segmentedquerystringNo1 declares that the client supports segmented retranslation; see Segmented Retranslation below (v1.18.0)
fromSidquerynumberNoOnly together with segmented=1: start from this sentence (inclusive); when omitted, start from the beginning (v1.18.0)
expectedRevisionquerynumberNoTranscript 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 segmentedWith segmented=1
ProcessingTranslates the whole transcript at once; saved and charged only when finishedStops at the current segment about 230 seconds after the request starts; what was translated is saved as usual
Transcript too longHTTP 422 retranslate_segmentation_required before the stream starts; no chargeNever returned; always segmented
doneFields unchangedAlso carries revision; when the segment was cut short, also truncated: true and nextSid
BillingCharged once for the whole transcriptEach segment is saved and charged separately, only for what that segment translated

Continuation flow

  1. Send ?targetLang=en-US&segmented=1.
  2. When done arrives:
    • No truncated: translation is finished.
    • truncated: true: send the next request with fromSid={nextSid} (you can also send expectedRevision={revision} to make sure the transcript was not changed by anything else in the meantime).
  3. Repeat until done no longer carries truncated.

Note: The parameter names are fromSid and expectedRevision. Sending from_sid or expected_revision, or fromSid without segmented=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 emit event: error carrying sid + 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
}
FieldTypeDescription
sidnumberSentence ID
textstringTranslation result
is_finalbooleanWhether 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": "..."
  }
}
FieldTypeDescription
error_codestringError code: sse_translation_failed or llm_content_filtered
severitystringSeverity. error for sse_translation_failed, warning for llm_content_filtered
messagestringHuman-readable message
contextstringError context. sse for sse_translation_failed, translation for llm_content_filtered
sidintNumber of the failed sentence
detailsobjectDebug information including translation_language, original_error, etc.
request_idstringIdentifier for this request; including it when reporting a problem speeds up diagnosis
timestampstringWhen the event occurred (ISO 8601)

Handle the two failures differently: llm_content_filtered means the sentence content cannot be translated and retrying will not change the result — revise the original text and try again. sse_translation_failed means the translation did not complete this time; retrying later usually succeeds. The two carry different severity and context values (see the table above). If you filter events by severity, make sure warning is 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
}
FieldTypeDescription
totalUpdatednumberTotal number of sentences updated (excluding failed sentences)
characters_billednumberCharacter count used as the billing basis for this request
chargedstringPoints consumed by this operation, calculated from the rate. This value reflects usage: usage already covered by an unlimited plan is still reported here
billedbooleanWhether this request incurred consumption; always true (all three fields are absent when nothing was consumed)
revisionnumberOnly with segmented=1: the transcript revision after this segment; can be used as expectedRevision for the next segment (v1.18.0)
truncatedbooleanOnly with segmented=1 and only when this segment was cut short; always true: some sentences are not translated yet (v1.18.0)
nextSidnumberAppears 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, and billed appear only when the request actually incurred consumption. When nothing was consumed (for example, when generation fails), all three are absent. Always use billed to determine whether a request was billed (billed only when billed is true) — 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 use charged directly instead of deriving it.

Specific Error Codes

Error CodeHTTP StatusDescriptionRecommended Action
sse_translation_failed500Translation failed (per-sid)The failed sentence is still reported via event: error; the overall flow is not interrupted
llm_content_filtered400The 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_found404Recording not foundVerify that taskId is correct
recording_not_completed422The recording has not finished processingWait for it to complete and retry
sse_transcript_not_found404Transcript not foundThe recording may not have finished processing
auth_insufficient_credit402Insufficient creditThis is a real HTTP 402 JSON response returned before the stream starts, not an SSE event; top up and retry
stt_quota_exceeded402Available credit does not cover the estimated cost of this requestAlso a JSON response returned before the stream starts; top up and retry
record_translation_not_allowed400Recording-only (record) tasks do not support translationAlso a JSON response returned before the stream starts; use a transcribe recording instead
storage_upload_failed500Failed to save the transcriptThe whole run is discarded and not billed; try again later. After this code you will not receive done
transcript_revision_conflict409Another 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_required422segmented=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 targetLang is missing or is not a supported language, or when segmented, fromSid, or expectedRevision has an invalid value or usage, the response is HTTP 200 with event: error, and data contains only message — no error_code, severity, context, request_id or timestamp. Read message directly, 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 error is 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

ParameterLocationTypeRequiredDescription
taskIdpathstringYesRecording ID (UUID)
targetLangquerystringYesTarget 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
}
FieldTypeDescription
textstringAccumulated translation result (streamed, grows gradually)
is_finalbooleanWhether this is the final result (the last item is true)

done

{
  "totalUpdated": 1
}
FieldTypeDescription
totalUpdatednumberAlways 1, meaning one summary has been translated; it does not mean anything was saved
truncatedbooleanPresent 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 done event does not include the characters_billed / charged / billed fields. Re-translating an existing summary incurs no additional charge; only full-text retranslation (previous section) is billed.

Specific Error Codes

Error CodeHTTP StatusDescriptionRecommended Action
sse_summary_not_found404Summary not foundThis recording has no summary
sse_summary_translation_failed500Summary 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_filtered400The summary content cannot be translatedRetrying will not help; revise the summary and try again
recording_not_found404Recording not foundVerify that taskId is correct
recording_not_completed422The recording has not finished processingWait for it to complete and retry
sse_transcript_not_found404Transcript not foundThe recording may not have finished processing
auth_insufficient_credit402Insufficient creditThis is a real HTTP 402 JSON response returned before the stream starts, not an SSE event; top up and retry
record_translation_not_allowed400Recording-only (record) tasks do not support translationAlso 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

ParameterLocationTypeRequiredDescription
taskIdpathstringYesRecording ID (UUID)
sidpathnumberYesSentence ID (1-based)
targetLangquerystringNoTarget 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
expectedRevisionquerynumberNoOptimistic lock: the current transcript revision; a mismatch returns transcript_revision_conflict
api_keyquerystringConditionalAPI 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"]
}
FieldTypeDescription
sidnumberSentence ID
revisionnumberThe new revision after the write (used for the next optimistic lock)
original_text_edited_atstring|nullTime the original text was edited (if this sentence has been edited)
languages_translatedarrayLanguage codes that were translated successfully
languages_failedarrayLanguage codes that failed to translate

Specific Error Codes

Error CodeHTTP StatusDescriptionRecommended Action
recording_not_found404Recording does not exist or does not belong to this userVerify that taskId is correct
recording_not_completed422Recording processing is not yet completeWait for the recording to complete, then try again
entry_not_found404The specified sentence was not foundVerify that sid is correct
entry_text_empty422The original text of this sentence is empty (whitespace-only counts as empty)Edit the original text via PATCH first
sse_translation_failed500A target language failed to translate (per-lang)That language appears in languages_failed in done; other languages are unaffected. Try again later
llm_content_filtered400A 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_credit402Insufficient creditThis is a real HTTP 402 JSON response returned before the stream starts, not an SSE event; top up and retry
record_translation_not_allowed400Recording-only (record) tasks do not support translationAlso a JSON response returned before the stream starts; use a transcribe recording instead
transcript_revision_conflict409Revision mismatch, or another write to the same transcript is in progressReload the transcript to get the latest revision, then try again
storage_upload_failed500Failed to save the transcriptNothing 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

Copyright © 2026