Skip to main content

Realtime Text Stream Translation API

Low-latency translation of streamed text over a single WebSocket connection — an incremental engine built specifically for text that arrives piece by piece. Push text fragments as they are produced (word by word, phrase by phrase); the engine accumulates them in a translation context and emits translations as soon as it decides a piece is final.

Use it to translate live captions, a user typing, chat messages, or the output of your own ASR — anything where text arrives gradually and you want the translation to start before the sentence ends.

Palabra API client

Consider using the Palabra API Python client (Palabra.text_translation()).

from palabra_ai import Palabra, TextTranslation

palabra = Palabra() # $PALABRA_API_KEY

async with palabra.text_translation("en", "es") as session:
await session.push_text("The weather ")
await session.push_text("is beautiful ")
await session.flush("today.") # finalize the utterance
async for event in session:
if isinstance(event, TextTranslation):
print(event.text) # "El clima es hermoso hoy."
break

Step 1. Get an API Key

Create an API Key on the Palabra API Keys page. See Authentication for details.

Step 2. Connect

Open a WebSocket to the endpoint below, passing your API Key as the token query parameter (or in the Authorization header). The server validates the key and creates a streaming session for the lifetime of the connection automatically. Session JWTs and ClientId/ClientSecret pairs are accepted the same way as in the other realtime APIs.

wss://stream.palabra.ai/text-api/v1/text-translation/stream?token=<API_KEY>

Step 3. Configure the session

All messages in both directions are JSON text frames with one envelope:

{ "type": "<message type>", "message_id": "<opaque string>", "data": { } }

message_id is chosen by you (e.g. an incrementing counter as a string) and is echoed back on the response that corresponds to the message — see Correlation.

Start by sending set_settings:

{
"type": "set_settings",
"message_id": "1",
"data": {
"lang_src": "en",
"lang_tgt": "es",
"model": "smt",
"context_size": 60,
"max_sentences_in_context": 4,
"min_tokens": 5
}
}
FieldDescription
lang_src / lang_tgtSource and target language codes
modelTranslation model; smt is the incremental low-latency engine (recommended and required for flush)
context_sizeHow many tokens of context the engine keeps (0–60)
max_sentences_in_contextSentence cap of the context (1–30)
min_tokensMinimal fragment size the engine translates eagerly (3–10)
styleOptional style hint for the translation
request_idOptional context name — pass the same value after a reconnect to resume the accumulated context (it is scoped to your account; other clients can never read it)

The server confirms with settings_response:

{
"type": "settings_response",
"message_id": "1",
"data": { "request_id": "…", "code": "stream_created", "message": "New translation session created", "model": "smt" }
}

set_settings can be sent again mid-session to adjust parameters (the language pair of an existing context cannot change).

Step 4. Send text

{ "type": "translate", "message_id": "7", "data": { "sentence_src": "The weather is ", "flush": false } }
  • sentence_src — the next text fragment, up to 5000 characters. Fragments are concatenated by the engine exactly as sent, so they do not have to be whole words — raw ASR deltas that cut a word mid-way are fine. In languages that use spaces, include the spaces yourself (they are required for the joined stream to read like the original text), and keep punctuation — it drives utterance segmentation.
  • flush: trueoptional latency control. The engine emits translations on its own as the context accumulates — you never have to flush. A flush tells the engine to answer now: everything accumulated but not yet translated is translated immediately, and the engine moves on to the next utterance. Natural moments are end-of-utterance signals (sentence end, speaker turn, your ASR's end-of-speech) — flushing there cuts the latency of the tail. A flush works in both forms:
    • with text{"sentence_src": "today.", "flush": true} pushes the last fragment and finalizes in one message;
    • without text{"sentence_src": null, "flush": true} just finalizes what has already been pushed.

To drop the accumulated context without closing the connection, send {"type": "clear_context", "message_id": "…", "data": {}} — the server replies with context_cleared.

Step 5. Receive translations

{
"type": "translation_result",
"message_id": "9",
"data": { "request_id": "…", "translation": "El clima es hermoso hoy.", "model_used": "smt" }
}

Correlation

The engine translates incrementally: a translate message may produce no reply at all — that means the fragment was accumulated into the context and is still waiting for enough material. The next translation_result you receive covers every fragment sent up to and including the message_id it carries. So to render a live translation, keep your fragments in send order and mark all of them translated whenever a result with an equal-or-higher message_id arrives.

Errors

{ "type": "error", "message_id": "7", "data": { "code": "VALIDATION_ERROR", "message": "…" } }
CodeMeaning
VALIDATION_ERRORMalformed envelope, unknown type, missing session or an over-long fragment
BAD_PARAM / BAD_REQUESTThe engine rejected the settings or the request (e.g. unsupported pair, language mismatch)
RATE_LIMIT_EXCEEDEDToo many translate messages (50 per second by default)
SERVICE_UNAVAILABLEThe translation backend is unavailable — reconnect and re-send set_settings (use the same request_id to resume the context)
QUOTA_EXCEEDEDBilling limit reached; the connection is closed

Limits & billing

  • One fragment ≤ 5000 characters; translate rate limit 50/second.
  • Connection rate limit 20 per minute per token.
  • Billing unit: characters of sentence_src at submit time.