Skip to main content
The Realtime Transcription API is available to Enterprise plan customers on request. See Overview for the requirements. This page covers the rules for the WebSocket connection you open to wsUrl after you create a session. The REST APIs to create, get, list, and end sessions are in the API reference.

How do sessions and connections differ?

A session is the transcription unit you create over REST. A connection is one WebSocket attached to that session. A session has only two states, OPEN and ENDED. While the session is OPEN, a connection can drop and attach again.
  • Once a session is ENDED, you cannot reopen it. To keep transcribing, create a new session.
  • When a connection drops, the session stays OPEN. Reconnect with the same wsUrl and streamToken.
  • You receive the reason the session ended in endReason. The values are listed under session.ended.

Connecting

Connect to the wsUrl from the POST /v1/external/realtime-transcriptions response with the Authorization: Bearer <streamToken> header.
  • Use wsUrl exactly as returned. It currently looks like wss://<host>/v1/external/realtime-transcriptions/{id}/stream, but the host and path can change.
  • Tiro accepts the token only in the Authorization header. You cannot connect with a query string, a cookie, or an API key. For that reason, the browser WebSocket API, which cannot set headers, cannot connect.
  • Tiro does not check the Origin header.
  • A streamToken works only for its session, and you use the same token to reconnect while the session is OPEN. You receive it only in the session creation response, and no API issues it again. If you lose the token, end the session and create a new one.
Tiro accepts the connection first. If something is wrong, it sends an error message and then closes the connection. Tiro rejects with an HTTP status only in these three cases. After accepting, Tiro checks the token, the session status, the API key that issued the token, and the workspace policy. If the policy is turned off or the API key is deleted during a session, Tiro keeps the open connection and rejects the next connection.

Audio frames

Send audio as binary frames and control messages as text frames. Each text frame carries one UTF-8 JSON object. You can send audio before you receive session.started. Tiro keeps audio that arrives before transcription is ready and processes it in order.

How is the audio lead calculated?

The audio lead is how far the audio you sent is ahead of the real elapsed time. Tiro measures it for each connection and closes the connection when it exceeds 60 seconds.
  • Idle time does not build up credit. If you send nothing for a minute and then send two minutes of audio at once, you still exceed 60 seconds.
  • A new connection inherits the lead of the previous one. Reconnecting does not reset the limit.
  • Above 60 seconds, retryAfterMs in error (audio_rate_exceeded) holds the overflow. Wait at least that long, reconnect, and send at real-time pace.

Messages

Every text message has a type field. Lengths and offsets are integer milliseconds with names that end in Ms. Times are ISO-8601 UTC strings with names that end in At. Locales use the Supported locales format, such as ko_KR. Ordering. Messages for the same segmentId (transcript) and the same translationId (translation) arrive in the order Tiro sent them. Zero or more isFinal: false messages come first, then one isFinal: true. Tiro does not guarantee order between different segments, or between transcript and translation. Compatibility. Tiro can add message types and fields. It does not remove fields or change their names, types, or meanings. Ignore, or apply a default to, any unknown type, field, reason, endReason, or status value.

session.end

The client sends this to end the session. You can send it more than once. Tiro ignores every copy after the first. For what happens next, see Ending a session.

session.started

The first message of each connection. It usually arrives in about 1 second, and rarely takes tens of seconds.

transcript

A transcription result.
  • When an isFinal: false message arrives for a segmentId, replace that segment’s whole text.
  • isFinal: true arrives exactly once per segment. After it, no more messages arrive for that segment.
  • A segment can arrive as isFinal: true only, with no isFinal: false before it.
  • Tiro decides segment size and does not guarantee it. Segments currently break where an utterance ends.

translation

A translation result. It arrives only when the session set translationLocale.
  • The replacement rule matches transcript. An isFinal: false message replaces the whole previous text for the same translationId, and isFinal: true arrives only once.
  • Translation pieces do not pair 1:1 with source segments. One piece can span several segments, and one segment can split across several pieces.
  • Join the text of final pieces in arrival order, with no separator, to get the translation. Pieces can break mid-word, so do not add spaces between them. A final piece that contains only whitespace is a word break, so join it as is.
  • A translation can arrive after its source is final, or before the source’s isFinal: false message.
  • In an auto-detect session, Tiro does not translate a segment whose transcript.locale equals translationLocale. In a session with a set language, speech in the target language can also go untranslated.

session.ended

Tells you that the session ended. This is the last message of the session. Tiro closes the connection with close 1000 after it. When you receive it, do not reconnect.
A session that ends while no connection is open has nowhere to send this message. In that case, check the status with GET /v1/external/realtime-transcriptions/{id}. Connecting to an ended session returns error (session_ended).

error

Reports an error.

Ending a session

Send session.end to receive every last result.
1

Send session.end

After the last audio frame, send {"type": "session.end"}. Tiro ignores audio sent after it.
2

Receive the remaining results

Tiro sends all remaining transcript and translation messages with isFinal: true. Segments and translation pieces that are not final yet are sent as final with their last text.
3

Receive session.ended

Tiro sends session.ended and closes the connection with close 1000.
Keep the connection open until you receive session.ended. Sending the remaining results takes up to 30 seconds, including translations. If it does not finish within 30 seconds, Tiro sends session.ended with the results sent so far and adds incomplete: true. When a session ends over REST or by a timeout while a connection is open, it ends in the same order. In those cases, audio that reached Tiro but has not started transcription yet may be left out of the results. If you only close the WebSocket connection, the session stays OPEN and holds one of the workspace’s concurrent session slots. If a connection simply drops, you also do not receive the results that were not final at that moment.

Timeouts

  • Text messages do not reset the no-audio timer.
  • While muted, you can keep the connection open and stop sending. If you send audio again within 15 minutes, the session continues.
  • Tiro can end a session later than the timeout: up to about 1 minute late with a connection, and up to about 3 minutes late with no connection.

Reconnecting

While a session is OPEN, you can reconnect with the same wsUrl and streamToken after a disconnect. Decide whether to reconnect based on the last message you received, not the close code.
  1. If you received session.ended, the session is over. Do not reconnect.
  2. If the error that closed the connection (fatal: true) had retryable: false, do not reconnect. Ignore earlier non-fatal errors (fatal: false, such as invalid_message) in this decision.
  3. In every other case, reconnect. That covers an error with retryable: true and a close with no message. Close 1012 is a server restart, so reconnect right away. For everything else, use backoff.
Start the backoff at 1 second, double it up to 30 seconds, and add ±20% jitter. If retryAfterMs is present, wait at least that long. While the session is OPEN, there is no limit on reconnection attempts. A successful reconnection returns session.started with resumed: true. If the session already ended, you receive error (session_ended, retryable: false).

How do I resend audio from a disconnect?

Tiro never asks for audio again. Audio that was not sent is not part of the session, so the client must buffer it and send it again.
  • While disconnected, keep up to the most recent 60 seconds of audio. Drop anything older.
  • After reconnecting, read audioOffsetMs from session.started. It is the audio length Tiro finished processing, so it can be shorter than what the previous connection actually sent. If a new connection replaces the previous one before Tiro notices that the previous one dropped, the value can be 0.
  • First send the buffered audio from the audioOffsetMs point of the session timeline onward, then continue with live audio. This stays within the 60-second audio lead.
  • Tiro does not check whether resent audio duplicates audio it already received.
  • Close 1012 arrives with no message, so some frames sent just before it may not have reached Tiro. In that case too, resend the audio after audioOffsetMs.
  • Discard segments and translation pieces that were still isFinal: false at the disconnect. Tiro does not send them again on the new connection.

What happens if I connect to the same session twice?

A session has one connection at a time. When a new connection arrives with the same token, Tiro uses the new connection and closes the old one.
  • The old connection receives error (connection_replaced, retryable: false) and closes with close 4409. Do not reconnect from the old connection’s client.
  • The new connection receives session.started with resumed: true and continues the session.
Tiro works this way so that the new connection works right away even when a mobile network switch drops the old connection before Tiro notices.

WebSocket errors

New reason values can appear. For an unknown reason, follow its fatal and retryable values.

Close codes

Use this table when you read logs. Decide on reconnection with the Reconnecting rules and not the close code. New close codes can appear.

REST errors

The session REST APIs return errors in the shared format from Error handling. Branch on detail.reason. For a response with no detail.reason, branch on the HTTP status. Errors for each endpoint are in the API reference.