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

# Streaming transcription (WebSocket)

> Message contract for wss://api.withhopper.com/stt/websocket

`WSS wss://api.withhopper.com/stt/websocket`

Streaming speech-to-text on [Nemotron ASR](/models#nemotron-asr), NVIDIA's open speech-to-text model served by Hopper. Configuration is query parameters at connect; audio is binary frames in; transcripts are JSON frames out. This page is the exact message contract — the [streaming guide](/stt/streaming) covers usage and includes a runnable client.

## Handshake

* **URL**: `wss://api.withhopper.com/stt/websocket`
* **Method**: GET upgrade
* **Auth**: `Authorization: Bearer sk_hopper_...` header, or `?api_key=` query parameter
* **Success**: `101 Switching Protocols`
* **Failure**: raw HTTP response in the [error envelope](/platform/errors), written before any frames — 400 `invalid_turn_detection` or `invalid_sample_rate`, 401 `invalid_api_key`, 402 `insufficient_quota`, 429 `rate_limit_exceeded`, 502 `upstream_unavailable`, 503 `model_offline`

## Query parameters

| Param                 | Type   | Default        | Constraint                                                                                                    |
| --------------------- | ------ | -------------- | ------------------------------------------------------------------------------------------------------------- |
| `api_key`             | string | —              | Alternative to the `Authorization` header                                                                     |
| `model`               | string | `nemotron-asr` | Active STT model id                                                                                           |
| `language`            | string | none           | Forwarded to the model                                                                                        |
| `keyterms`            | string | none           | Comma-separated bias terms, forwarded to the model                                                            |
| `sample_rate`         | int    | `16000`        | 8000–48000 and in the model's allowlist (`nemotron-asr`: 8000, 16000, 22050, 24000, 44100, 48000)             |
| `turn_detection`      | string | `balanced`     | `balanced` \| `patient` \| `responsive` — preset values in the [guide](/stt/streaming#turn-detection-presets) |
| `start_threshold`     | number | preset         | Overrides the preset's speech-start threshold                                                                 |
| `eager_end_threshold` | number | preset         | Overrides the preset's eager end-of-turn threshold                                                            |
| `end_threshold`       | number | preset         | Overrides the preset's end-of-turn threshold                                                                  |
| `end_timeout_ms`      | int    | preset         | Overrides the preset's end-of-turn timeout                                                                    |

## Client → server

**Audio chunk** — binary frame. Raw PCM16 mono samples at the configured `sample_rate`. Send only after `Ready`. Received bytes are what gets metered: `audio_seconds = bytes / (sample_rate × 2)`.

**Flush** — text frame containing exactly `END`. Finalizes all buffered audio; the server sends any remaining finals, then echoes `END`.

## Server → client

**Ready** — sent once when the upstream session is live. Audio sent before it is not guaranteed to be processed.

```json theme={null}
{ "type": "Ready" }
```

**Transcript** — partial while a turn is in progress (`is_final: false`, revised as audio arrives), final when the turn ends (`is_final: true`, stable). Finals carry `words` when available.

```json theme={null}
{
  "text": "hello world",
  "is_final": true,
  "words": [
    { "word": "hello", "start": 0.12, "end": 0.48 },
    { "word": "world", "start": 0.52, "end": 0.9 }
  ]
}
```

| Field      | Type    | Notes                                               |
| ---------- | ------- | --------------------------------------------------- |
| `text`     | string  | Transcript text for the current segment             |
| `is_final` | boolean | Partials are revised; finals are stable             |
| `words`    | array   | Finals only. `{word, start, end}`, times in seconds |

**Flush acknowledgment** — text frame `END`, echoed after the final transcript for the flushed audio. Everything sent before your `END` is now transcribed.

## Billing and close

Usage costs \$0.30 per hour of audio, computed from received bytes, and settles every 60 s during the session and at close. At zero balance the server closes the socket with code **1008** ("insufficient credits").
