> ## Documentation Index
> Fetch the complete documentation index at: https://developers.telnyx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Live Transcript

> Follow the meeting transcript in real time via WebSocket or read it incrementally with long polling.

While the meeting runs, the bot captures audio and Telnyx STT transcribes it in real time. You have two ways to read segments as they are produced: a WebSocket stream (alpha) or incremental long polling.

## Read the Transcript Incrementally

`GET /v2/meeting_sessions/{id}/transcript` returns segments in ascending `seq` order, at most `limit` per page (default 100, maximum 1,000). Use `after` to page forward and `wait_seconds` to hold the request open (up to 25 seconds) so new segments arrive as soon as they are produced:

```bash theme={null}
curl -H "Authorization: Bearer $TELNYX_API_KEY" \
  "https://api.telnyx.com/v2/meeting_sessions/mtgsess_9b2f.../transcript?after=40&wait_seconds=20"
```

```json theme={null}
{
  "data": [
    {
      "seq": 41,
      "text": "let's ship it",
      "speaker_label": "Ada L.",
      "confidence": 0.94,
      "relative_ts": 1240.5,
      "occurred_at": "2026-06-16T09:00:42Z"
    }
  ],
  "meta": {
    "next_after": 41
  }
}
```

`wait_seconds` turns the call into a long poll: if no new segments exist yet, the request waits and returns as soon as a new segment lands or the timeout elapses (an empty `data` array with `"next_after": null`). Pass `meta.next_after` as the next request's `after` cursor; on an empty page keep the cursor you already had rather than replacing it with null.

## Connect to the WebSocket Stream

The stream endpoint replays stored events and transcript segments, then follows the meeting live. Open a connection with your API key on the upgrade:

```
WSS /v2/meeting_sessions/{id}/stream?after_seq=0&after_transcript_seq=0
```

Send your API key in the `Authorization` header using the Bearer scheme on the upgrade request. Header auth is the only supported mechanism -- query-string credentials such as an `access_token` parameter are rejected with `401`. Browser-native WebSocket clients cannot set the `Authorization` header during the handshake, so connect from a backend and relay to the browser if needed.

A successful upgrade returns `101`. Before upgrade, the server can return `401` for missing or invalid credentials or a nonexistent, foreign, or already-terminal session; `403` for a forbidden credential; `429` with `Retry-After` when authentication is overloaded; or `503` while authentication is unavailable.

<Warning>
  This is a WebSocket upgrade endpoint, not an ordinary HTTP `GET` route. A plain `curl` request -- or a request with only partial upgrade headers -- can reach the Meeting service and return an HTML `Cannot GET` response even when the WebSocket route is available. Use a WebSocket client that performs the complete handshake; verify a successful connection by the `101 Switching Protocols` response and incoming frames.
</Warning>

For example, connect from a backend terminal with [`wscat`](https://github.com/websockets/wscat). The Meeting Session must belong to the API-key account and must not be in a terminal state:

```bash theme={null}
export SESSION_ID='mtgsess_...'

npx --yes wscat \
  --connect "wss://api.telnyx.com/v2/meeting_sessions/${SESSION_ID}/stream?after_seq=0&after_transcript_seq=0" \
  --header "Authorization: Bearer [REDACTED]"
```

A successful connection prints `Connected (press CTRL+C to quit)` and begins receiving JSON frames. To test bidirectional commands while the session status is `active`, enter a command such as:

```json theme={null}
{ "action": "send_chat", "text": "WebSocket connection verified." }
```

The meeting receives the chat message and the socket receives the corresponding `chat.sent` event. Press <kbd>Ctrl</kbd>+<kbd>C</kbd> to disconnect.

Set `after_seq` and `after_transcript_seq` to the last sequence numbers you have seen. The server replays stored records after those cursors, then switches to live delivery. Replay is bounded: each connection replays at most 1,000 event rows and 1,000 transcript rows, and a `{"kind": "truncated", "store": "events" | "transcript"}` frame signals that more rows remain -- reconnect with that store's cursor set to the last sequence received, or backfill through the REST endpoints. Catch-up and live delivery can overlap, so tolerate duplicates; catch-up event frames carry a durable top-level `seq` while live event frames may not, and a sequence-less live frame must not advance your durable cursor.

Transcript segments arrive in two shapes, and a client has to handle both. Segments **replayed from the transcript store** during catch-up arrive as standalone `transcript` frames:

```json theme={null}
{
  "kind": "transcript",
  "seq": 17,
  "text": "Hello, can everyone hear me?",
  "confidence": 0.98,
  "speaker_label": "Alice",
  "relative_ts": 12.4,
  "occurred_at": "2026-06-16T09:00:12Z"
}
```

Segments produced **live** during the meeting arrive instead as event frames, with the segment nested under `payload`:

```json theme={null}
{
  "kind": "event",
  "type": "transcript.segment",
  "payload": {
    "seq": 18,
    "text": "Yes, loud and clear.",
    "confidence": 0.97,
    "speaker_label": "Bob",
    "relative_ts": 14.9
  },
  "occurred_at": "2026-06-16T09:00:14Z"
}
```

A client that branches only on `kind == "transcript"` receives nothing while a meeting is live -- silently, with no error -- because every segment produced after connection is an event frame.

Other frame kinds (`event`, `error`, `truncated`) and the complete frame protocol -- payload schemas, commands, and close codes -- are specified in the [Meeting Session agent stream reference](/api-reference/websockets/meeting-session-agent-stream).

Segment fields (`seq`, `text`, `speaker_label`, `confidence`, `relative_ts`, `occurred_at`) are documented on [List meeting session transcript](/api-reference/meeting-session-data/list-meeting-session-transcript).

### Sending Commands over the Stream

You can make the bot act from the same socket. Send a JSON frame:

```json theme={null}
{ "action": "speak", "text": "Let's recap what we agreed on.", "interrupt": false }
```

```json theme={null}
{ "action": "send_chat", "text": "Noted. I will send the summary after the call." }
```

The stream allows at most 8 commands in flight or queued, and there is no per-command acknowledgement. Inbound frames larger than 32 KiB close the connection with code `1009`, and if the server's outbound buffer backs up it closes with code `1013` and reason `backpressure` -- reconnect with your last durable cursors. For durable, acknowledged control prefer the [REST actions](/docs/meeting/interact).

### Availability

The WebSocket stream is currently an **alpha** feature and is not covered by a stability or availability commitment. The long-polling `transcript` endpoint above works on the standard API and is the supported way to read the transcript today.

## Related

* [Session Events](/docs/meeting/events) -- the stored event history behind the stream
* [Join a Meeting](/docs/meeting/join-meeting) -- create a meeting session and send the bot
* [Meeting Presence](/docs/meeting/interact) -- control how the bot appears and acts in the meeting, from its roster name to speaking and chat
* [Collect Results](/docs/meeting/collect-results) -- read the finalized transcript and summary
