Summary Translation 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} (this endpoint does not accept the key in the query string) |
Note: This endpoint accepts POST only (JSON body), so the browser's native EventSource API cannot be used. Use the fetch API with a ReadableStream, or any HTTP client that can read a streaming response.
Endpoint Overview
| Method | Endpoint | Result saved | Billed | Purpose |
|---|---|---|---|---|
| POST | /api/v1/sse/summary/translate | No | Yes | Translate summary text supplied in the request into the specified language (not tied to any recording) |
How it differs from Retranslate Summary:
- Retranslate Summary: the input is the summary saved on the server for that recording, and it is not billed.
- This endpoint: the input is the
contentin the request, for content the server does not have, such as a merged summary or a summary edited by the user.
Both translate the same way. The result is not saved and is only streamed back to the client.
Added in v1.17.0.
Request Parameters (JSON body)
| Parameter | Type | Required | Limits | Description |
|---|---|---|---|---|
content | string | Yes | ≤30,000 characters, not whitespace only | The summary text to translate |
target_language | string | Yes | A language code from Supported Languages | Target language |
source_language | string | No | A language code from Supported Languages | Source language. Detected automatically when omitted. Returns 422 if it is the same as target_language |
idempotency_key | string | Yes | ≤64 characters, A-Z a-z 0-9 . _ - | Duplicate-request identifier that prevents double charging. Same rules as Ad-hoc Summary |
Error Response Pattern
This endpoint is meant for backend integrations. Errors before the stream starts always return a real HTTP status code (JSON body), the same as Ad-hoc Summary:
| Stage | Response |
|---|---|
| Authentication failure (401/403), validation failure (422), insufficient credit (402), identifier conflict (409), rate limit or free-retry limit exceeded (429) | Real HTTP status code with a JSON error body |
| After the stream starts (translation failure, timeout, content filtered, and so on) | HTTP 200 with an SSE error event |
A 422 JSON body lists the per-field messages in data.details.errors.
Billing
- Billed by the character count of
content: 0.1 credits per 200 characters (a partial unit counts as a full 200 characters). This is the same rate as full-transcript retranslation. - Examples: 450 characters = 0.3 credits; 30,000 characters = 15 credits.
- Billed only on success. A request that ends with
erroris not billed. - A truncated translation (see "Processing Time and Truncation" below) is billed as usual; a translation judged to be unfinished is not billed. Always check
billedindoneto see whether credits were actually deducted.
Duplicate Requests and Billing Guarantees
The system uses the entire request (content, target_language, source_language) to decide whether two requests are retries of the same job:
| Situation | Behavior |
|---|---|
| Same identifier, identical request | Not billed again, but the translation is run again rather than replaying the previous result |
| Same identifier, any field different (including only the target language) | Returns 409 summary_idempotency_key_conflict; not translated, not billed |
| Retry after the first request failed, or after it was judged unfinished | The identifier is not consumed; the retry is treated as a new request |
idempotency_keyis scoped to a single API key. To translate new content or switch to another language, send a newidempotency_key.
Request Example
curl -N -X POST "https://vas-poc.vurbo.ai/api/v1/sse/summary/translate" \
-H "X-API-Key: vas_aB3dE5fG7hI9jK1lM3nO5pQ7rS9tU1vW" \
-H "Content-Type: application/json" \
-d '{
"content": "## 會議摘要\n\n1. 產品上線時程確認為下月初",
"target_language": "ja-JP",
"source_language": "zh-TW",
"idempotency_key": "summary-7f3a-ja"
}'
Event Sequence
1. connected → connection confirmed
2. summary_translation → translated text (repeated N times, cumulative)
3. done → translation complete
or
error → translation failed (the stream stops and done is not sent)
connected
{ "message": "Summary translation stream connected (target_language: ja-JP)" }
summary_translation
{ "text": "## 会議の要約\n\n1. 製品のリリース時期は来月初めに確定", "is_final": false }
| Field | Type | Description |
|---|---|---|
text | string | The full translation accumulated so far. Each event extends the previous one |
is_final | boolean | Whether this is the last event. The last event's text is the complete content delivered for this request; if done carries truncated, the translation is incomplete |
How long content is streamed: when the content is long, the stream often delivers a large block at once (possibly thousands of characters), and there may be a pause of a few seconds between events. Each event is still the cumulative full text, in the original order.
If you want to render the text character by character, smooth it on the client side.
done
{
"tokens_used": 230,
"source_language": "zh-TW",
"target_language": "ja-JP",
"characters_billed": 24,
"charged": "0.1",
"idempotency_key": "summary-7f3a-ja",
"billed": true
}
| Field | Type | Description |
|---|---|---|
tokens_used | number | Total tokens used |
source_language | string | null | The source language sent in the request; null when it was omitted |
target_language | string | Target language |
characters_billed | number | Billing basis (the character count of content) |
charged | string | Credits consumed, calculated from the rate. This value reflects usage: it is still reported for usage covered by an unlimited plan, for free retries, and when the translation is judged unfinished. Check billed to see whether credits were actually deducted |
idempotency_key | string | The identifier sent in the request, echoed back for reconciliation |
billed | boolean | Whether credits were actually deducted for this request. It is false for a free retry with the same identifier and when the translation is judged unfinished. Reconcile against this field |
truncated | boolean | Present only when the translation is incomplete (the value is always true); absent when the translation is complete. There are two causes: the translation hit the processing-time or length limit and was cut off (billed as usual), or the translation is clearly shorter than the source and was judged unfinished (not billed). If this request is not a retry with the same identifier, billed tells them apart: true means cut off, false means judged unfinished |
When you receive
truncated: true, the last event'stextis not the complete translation. We recommend not saving it as the final translation; you can prompt the user to retry.
Processing Time and Truncation
- A request has a processing-time limit of about 230 seconds, counted from when the request is sent.
- When the limit is reached, if some translation has already been received, the current content is sent as truncated:
donecarriestruncated: trueand the request is billed as usual. If nothing has been received yet, anerror(Translation timed out) is sent and the request is not billed. - If no new content arrives for more than 60 seconds during translation, an
error(Translation timed out) is sent and the request is not billed.
Endpoint-Specific Error Codes
Before the stream starts (real HTTP status code with JSON):
| Error code | HTTP | Description | Suggested handling |
|---|---|---|---|
validation_failed | 422 | Validation failed, for example a missing field, more than 30,000 characters, whitespace only, an unsupported language, the same source and target language, or invalid text encoding | Fix the fields listed in data.details.errors |
summary_idempotency_key_conflict | 409 | The same idempotency_key was already used for different request content | Use a new idempotency_key |
stt_quota_exceeded | 402 | Available credit is not enough for the amount due | Top up and retry |
too_many_requests | 429 | Rate limit exceeded, or the free-retry limit for the same idempotency_key has been reached (24-hour window). A 429 received when resending the same identifier means the free-retry limit | For the rate limit, retry later; for the free-retry limit, use a new idempotency_key and do not retry in a short loop |
After the stream starts (HTTP 200 with an SSE error event):
| Error code | details.original_error | Description | Suggested handling |
|---|---|---|---|
sse_summary_translation_failed | Translation 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 | Retry later |
sse_summary_translation_failed | Empty translation | The translation result was empty. For long content, part of the translation may already have been received | Check the content and retry |
sse_summary_translation_failed | Translation service unavailable | The translation service is temporarily unavailable, or the connection was interrupted | Retry later |
sse_summary_translation_failed | Service error | Other errors, including being unable to reach the translation service at all | Retry later; if it persists, contact us with the request_id |
llm_content_filtered | Content filtered | The content cannot be translated | Retrying will not help; revise the content |
None of these errors are billed, and none of them consume the identifier.
Version: V1.24.1 Last Updated: 2026-09-28