> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Live transcript streaming

> Consume a call's live transcript over Server-Sent Events: the event contract, keepalive cadence and reconnect behaviour, redaction guarantees, and the failure modes supervisor, QA, and agent-assist consumers should handle.

# Live transcript streaming

`GET /api/v1/voice/calls/:callId/transcript/stream` delivers a call's transcript utterance by utterance while the call is in progress. It is a one-way Server-Sent Events (SSE) channel for supervisor consoles, QA tooling, and agent-assist integrations that need the words as they are spoken, not a finished document after the call.

## Why this surface exists

The stream publishes each partial and final transcript frame as soon as the speech pipeline produces it, with live redaction applied before broadcast. It is deliberately not the post-call pipeline:

* **Post-call transcription and recordings** produce a complete transcript once the call ends. Use the [recording library](/voice/recording-library) when you need the finished, normalised document, search across calls, or exports. The [recording lifecycle](/concepts/recording-lifecycle) covers what happens to recordings and their transcripts after hang-up.
* **This surface is live-redact-then-stream.** There is no batch step and no webhook delivery: frames only arrive while an SSE connection is open. If your integration can wait for the call to end, use the post-call surfaces instead of holding a stream open.

A delegated supervisor variant with the same contract is available at `GET /api/v1/voice/supervisor/calls/:callId/transcript/stream` for supervisor views of agent calls. See the [supervisor plane model](/concepts/voice-supervisor-plane-model) for how delegated supervision is structured.

## Endpoint contract

| | |
| - | - |
| Method and path | `GET /api/v1/voice/calls/:callId/transcript/stream` |
| Path parameter | `callId`, the call identifier from your call logs |
| Authentication | Your API key in the `X-API-Key` header, or the same key as a `?token=` query parameter for browser `EventSource` clients, which cannot set request headers |
| Response content type | `text/event-stream` (`Cache-Control: no-cache, no-transform`, unbuffered) |

Errors returned before the stream opens:

| Status | Meaning |
| - | - |
| 401 | No tenant context. Send your API key, or pass it as `?token=`. |
| 400 | `callId` is missing or malformed. |
| 404 | The call does not exist in your organization. |
| 403 | The request carries a browser `Origin` header that is not a trusted Orbit origin. |

Browser note: when a request includes an `Origin` header, it must match a trusted Orbit origin or the request is refused with 403 before the stream opens. Server-to-server clients that send no `Origin` header are unaffected.

### Events

Every event carries a monotonically increasing integer `id`. Lines starting with `:` are comment keepalives; `EventSource` ignores them automatically.

| Event | Data | When it fires |
| - | - | - |
| `connected` | `{ callId, status }` | First frame after the stream opens. Confirms the pipeline is live even before any speech has been recognised. |
| `status` | `{ status, terminal }` | The call's current status at open time. `terminal: true` means the call has ended. |
| `transcript` | Transcript frame, see below | Once per utterance update: the hydration burst at open, then every live frame. |
| `end` | `{ reason }` | The server is closing the stream. `reason` is `terminal` (the call ended) or `max-duration` (the 30-minute session cap; reconnect to continue). |

Keepalive cadence: the server writes a `: ping` comment every 15 seconds so load balancers and corporate proxies with idle timeouts do not sever the connection during a quiet stretch of an active call.

### Transcript frame shape

| Field | Type | Notes |
| - | - | - |
| `type` | `"partial"` or `"final"` | Partials are in-flight recognitions and may be revised or repeated; finals are stable. |
| `speaker` | string | Label of the speaking party. |
| `text` | string | The utterance as subscribers are allowed to see it. See redaction guarantees below. |
| `ts` | number | Unix epoch milliseconds. |
| `confidence` | number, optional | 0 to 1, present when the speech provider reports it. |

### Lifecycle

On connect the stream writes `connected`, then `status`, then a hydration burst of every frame already captured for the call, so a subscriber joining mid-call does not start from a blank slate. If the call is already over (`completed`, `failed`, `busy`, `no-answer`, or `canceled`), the server sends the hydration burst followed by `end` with `reason: "terminal"` and closes.

For a live call the stream stays open until the caller hangs up, the client disconnects, the publisher signals end of transcript, or the 30-minute session cap elapses. After a `max-duration` end, reconnect; hydration replays the captured transcript so nothing already said is lost.

## Redaction guarantees

If your organization has live transcript redaction enabled in its compliance settings (the same policy that governs recording redaction), redaction is applied before broadcast:

* Every hydrated frame and every live frame is masked before it is written to the stream. The redacted frame is all a subscriber ever sees; with redaction enabled, a spoken payment card number, social security number, phone number, email address, or IP address (or every supported category, when your policy does not restrict the set) never reaches a subscriber.
* The category set is resolved once, when the stream opens, from the same tenant policy the recording pipeline uses, so the live stream and the stored recording cannot disagree about what is masked.
* Redaction is a tenant setting, not a client option. Subscribers cannot request more or less redaction per connection, in real time or otherwise; a change to your policy applies to streams opened after the change.
* When redaction is disabled for your organization, frames pass through unchanged.

## Subscribe from a client

```js theme={null}
const source = new EventSource(
  `https://api.orbit.devotel.io/api/v1/voice/calls/${callId}/transcript/stream?token=${API_KEY}`,
);

source.addEventListener("connected", (e) => {
  const { status } = JSON.parse(e.data);
  console.log("stream live, call status:", status);
});

source.addEventListener("transcript", (e) => {
  const frame = JSON.parse(e.data);
  if (frame.type === "final") appendLine(frame.speaker, frame.text);
  else renderPartial(frame.speaker, frame.text);
});

source.addEventListener("end", (e) => {
  const { reason } = JSON.parse(e.data);
  if (reason === "max-duration") reconnectWithBackoff();
  // "terminal" means the call is over; do not retry.
});
```

Rules for a robust subscriber:

* The browser `EventSource` reconnects on its own after a drop. If you build your own client, use exponential backoff capped at roughly 30 seconds, and stop retrying after a `terminal` end, a `terminal: true` status, or a 404.
* Reconnects replay the hydration burst, so de-duplicate against the last frame you rendered. Do not assume a zero gap: frames published while you were disconnected are not re-delivered.
* Register listeners only for the event names you handle and ignore the rest. New event types may be added over time; treat unknown names as no-ops, never as errors.
* `?token=` puts your key in the URL. Query strings are stripped from access logs platform-wide, but keep the key in server-side or short-lived session contexts in production.

## Failure modes

* **The stream closes unexpectedly.** Proxies, sleeping laptops, and load balancers all sever SSE connections. Treat any close as recoverable unless it follows `end` with `reason: "terminal"` or a 404: reconnect and let hydration restore history.
* **Silence on a live call.** Live frames ride a push channel that is not replayed. If the event bus blips, frames published during the blip are not re-delivered; the next reconnect hydrates whatever was captured. A run of `: ping` keepalives with no transcript events means the connection is healthy; it does not mean speech stopped.
* **Transcription not enabled.** When live transcription is off for the call, the stream opens and delivers `connected`, `status`, and keepalives, but no `transcript` events. Silence is a signal: check your transcription configuration (see [Live call transcription](/voice/live-call-transcription)) rather than the stream.
* **The 30-minute cap.** `end` with `reason: "max-duration"` is routine housekeeping, not an error. Reconnect to continue.

This surface is SSE only. Orbit does not POST transcript frames to a webhook; a subscriber that needs durability should persist the frames it receives.

## Example: supervisor console

Render the rolling transcript alongside the call card:

```js theme={null}
function attachTranscript(callId, cardEl, sessionToken) {
  const box = document.createElement("div");
  cardEl.appendChild(box);

  const source = new EventSource(
    `/api/v1/voice/calls/${callId}/transcript/stream?token=${sessionToken}`,
  );

  let partialEl = null;
  source.addEventListener("transcript", (e) => {
    const frame = JSON.parse(e.data);
    if (frame.type === "final") {
      if (partialEl) {
        partialEl.remove();
        partialEl = null;
      }
      const line = document.createElement("p");
      line.textContent = `${frame.speaker}: ${frame.text}`;
      box.appendChild(line);
    } else if (partialEl) {
      partialEl.textContent = `${frame.speaker}: ${frame.text} ...`;
    } else {
      partialEl = document.createElement("p");
      partialEl.textContent = `${frame.speaker}: ${frame.text} ...`;
      box.appendChild(partialEl);
    }
  });
  source.addEventListener("end", () => source.close());
  return () => source.close();
}
```

Because redaction happens before broadcast, the supervisor console above never needs its own masking logic: what the tenant policy masks is what renders.

## Related

* [Live call transcription](/voice/live-call-transcription): how speech-to-text works during a call.
* [Live-call speech failover](/voice/live-call-stt-failover): what provider failover means for a live transcript.
* [Supervisor plane model](/concepts/voice-supervisor-plane-model): delegated supervision, including the supervisor transcript surface.
* [Recording lifecycle](/concepts/recording-lifecycle): the post-call path for recordings and transcripts.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.