> ## 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.

# Noise Suppression WebSocket API

> Remove background noise from raw PCM audio in real time over a WebSocket connection. Endpoint, authentication, query parameters, models, audio format, end of stream, and close codes.

Stream raw PCM audio over a WebSocket and receive the same audio back with background noise removed. The service runs AI noise suppression on Telnyx infrastructure. It is independent of calls: use it for any audio source, for example before you send audio to a speech-to-text engine.

To remove noise from a live Voice API or TeXML call, use [in-call noise suppression](/docs/voice/programmable-voice/noise-suppression) instead. This page covers the standalone WebSocket API.

## Endpoint

```
wss://api.telnyx.com/v2/noise_suppression
```

## Authentication

Send your API key in the `Authorization` header of the WebSocket upgrade request:

```
Authorization: Bearer YOUR_API_KEY
```

A request without valid credentials fails with HTTP `401` before the WebSocket opens.

## Connection lifecycle

### 1. Handshake

Set all options as query parameters on the upgrade request. You cannot change them during the session.

```
GET /v2/noise_suppression?format=l16&sample_rate=16000&channels=1 HTTP/1.1
Host: api.telnyx.com
Upgrade: websocket
Connection: Upgrade
Authorization: Bearer YOUR_API_KEY
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
```

### 2. Streaming

Send audio as **binary** frames. For each binary frame you send, the server returns exactly one message:

* a **binary** frame with denoised audio, or
* a **text** frame with a JSON error (see [Errors](#errors)).

```
Client → Server  binary: 640 bytes (20 ms)
Client ← Server  binary: 640 bytes denoised
Client → Server  binary: 640 bytes (20 ms)
Client ← Server  binary: 640 bytes denoised
```

### 3. End of stream

When you have no more audio, send this text frame:

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

The server then returns all remaining audio, including the audio held back by the model's processing delay, as one final binary frame. Then it closes the connection with close code `1000`. Keep reading until you receive the close frame.

```
Client → Server  text:   {"type":"end_of_stream"}
Client ← Server  binary: remaining denoised audio
Client ← Server  close:  1000 end_of_stream
```

Always send `end_of_stream` before you stop. If you close the WebSocket without it, the server cannot send the remaining audio, and the end of your stream is lost.

## Query parameters

| Parameter | Type | Default | Description |
| - | - | - | - |
| `format` | string | `l16` | Audio encoding. `l16` or `f32le`. Used for both input and output. |
| `sample_rate` | integer | `16000` | Sample rate in Hz. Any integer from `8000` to `48000`. Used for both input and output. See [Sample rates](#sample-rates). |
| `channels` | integer | `1` | Number of channels. Only `1` (mono) is supported. |
| `model` | string | `rook` | Noise suppression model. `rook` or `quail`. See [Models](#models). |
| `quality` | string or number | `balanced` | Enhancement strength. See [Quality](#quality). |

An unsupported value closes the connection with an `invalid_params` error and close code `1008`. See [Errors](#errors).

## Sample rates

The service accepts any integer sample rate from `8000` to `48000` Hz. A value outside that range closes the connection with an `invalid_params` error and close code `1008`. The output uses the same sample rate as the input.

Use the native sample rate of your audio. Send telephony and PSTN (narrowband) audio at `8000`.

Do not upsample 8 kHz audio to a higher rate. The full-band model treats the missing upper band as noise and lowers the speech level by about 14 dB.

## Models

You select the model family with `model`. The service selects the model variant that matches your `sample_rate`.

| `model` | Use for | `8000` | `8001` to `16000` | `16001` to `48000` |
| - | - | - | - | - |
| `rook` (default) | Audio for human listeners | 8 kHz model | 16 kHz model | 48 kHz model |
| `quail` | Audio for speech-to-text | 8 kHz model | 16 kHz model | 16 kHz model |

Above `16000`, `quail` processes the audio with the 16 kHz model. The output keeps your sample rate, but its audio bandwidth is limited to the 16 kHz model's bandwidth (8 kHz).

## Quality

`quality` sets the enhancement strength: how much noise the model removes.

| Value | Enhancement strength |
| - | - |
| A number from `0.0` to `1.0` | The given strength. `0.0` is the lightest processing, `1.0` is the strongest. |
| `low_latency` | `0.5`. Lighter processing. The name is kept for compatibility. |
| `balanced` | `0.8`. The default. |
| `high_quality` | `1.0`. The strongest processing. |

Any other value, for example `1.5` or `max`, closes the connection with an `invalid_params` error.

## Audio format

Audio is raw mono PCM with no container and no header. Do not send WAV headers. Strip the header and send only the sample data.

| `format` | Sample type | Bytes per sample | Byte order |
| - | - | - | - |
| `l16` | 16-bit signed integer | 2 | little-endian |
| `f32le` | 32-bit float, range -1.0 to 1.0 | 4 | little-endian |

The output uses the same `format` and `sample_rate` as the input.

`l16` is little-endian. This is different from the big-endian L16 of RTP (RFC 3551). Byte-swap RTP payloads before you send them.

## Chunk size

* Each binary frame must contain a whole number of samples: a multiple of 2 bytes for `l16`, a multiple of 4 bytes for `f32le`.
* The maximum binary frame size is **65,536 bytes**.
* 20 ms chunks are a good default. At 16 kHz `l16`, 20 ms is 640 bytes. At 8 kHz `l16`, 20 ms is 320 bytes.

## Buffering and latency

The model processes audio in fixed-size internal frames, and it has a short processing delay. The server keeps input that does not fill a complete frame and processes it together with your next chunk. As a result:

* A response can be smaller or larger than the matching request. It can also be an **empty** binary frame. Ignore empty frames.
* The output starts with the model's processing delay. The first samples you receive correspond to that delay, not to your first input samples.
* When you send `end_of_stream`, the server returns all of the audio that it still holds. The total output is your input length plus the processing delay.

## Session duration

Sessions are limited to 590 seconds (just under 10 minutes). Reconnect to continue longer streams.

At the limit, the server returns the remaining audio, sends a `max_session_duration` error message, and closes the connection with code `1008`. You do not lose audio. To end with a normal close (`1000`) instead, send `end_of_stream` before the limit and then open a new connection for the rest of the stream.

## Errors

### HTTP errors (handshake)

| Code | Cause |
| - | - |
| 400 | `sample_rate` or `channels` is not a valid non-negative integer |
| 401 | Missing or invalid API key |
| 503 | The server is restarting and does not accept new sessions. Reconnect with backoff. |

### Error messages

The server sends errors as JSON text frames:

```json theme={null}
{
  "type": "error",
  "code": "invalid_params",
  "message": "unsupported sample_rate 96000 (supported: 8000-48000 Hz)"
}
```

`code` is a stable identifier. `message` is a human-readable description; do not parse it.

### Error codes

| Error code | Close code | Cause |
| - | - | - |
| `invalid_params` | 1008 | A query parameter has an unsupported value: `format`, `sample_rate`, `channels`, `model`, or `quality`. |
| `max_session_duration` | 1008 | The session reached the 590-second limit. The server returns the remaining audio before it closes. |
| `session_error` | 1011 | The server could not start the noise suppression session. |
| `processing_error` | 1011 | The noise suppression engine failed. |
| `encode_error` | 1011 | The server could not encode the denoised audio. |
| `server_shutdown` | 1001 | The server is restarting, for example during a deploy. The server returns the remaining audio before it closes. |
| `max_connections` | 1013 | The server has too many open sessions. Try again later. |
| `overloaded` | 1013 | The server is overloaded and does not accept new sessions. Try again later. |
| `chunk_too_large` | None, the session continues | The binary frame is larger than 65,536 bytes. The server drops the chunk. |
| `decode_error` | None, the session continues | The frame length is not a whole number of samples. The server drops the chunk. |
| `unsupported_message` | None, the session continues | The server does not recognize the text frame. The only supported text frame is `end_of_stream`. |

### Close codes

When the server closes the connection, the close frame reason is the error code. Before every close that the server makes with a code other than `1000`, including at the [session limit](#session-duration), it sends the matching error message. A close caused by a network failure comes without an error message.

| Close code | Reason | What to do |
| - | - | - |
| 1000 | `end_of_stream` | Nothing. Normal end: the server returned all audio after your `end_of_stream`. |
| 1001 | `server_shutdown` | The server is restarting. Reconnect with backoff. |
| 1008 | `invalid_params` | A query parameter has an unsupported value. Fix the parameter before you reconnect. |
| 1008 | `max_session_duration` | The session reached the 590-second limit. Reconnect to continue. |
| 1011 | `session_error`, `processing_error`, `encode_error` | Server error. Reconnect and try again. |
| 1013 | `max_connections`, `overloaded` | The service is busy and does not accept new sessions. Open sessions continue. Reconnect after a short wait. |

### Reconnecting

Close code `1001` and HTTP `503` mean that the server is restarting, for example during a deploy. Close codes `1011` (server error) and `1013` (service busy) are also temporary. None of these are errors in your request. Reconnect with exponential backoff; the new session goes to a healthy server. The new session starts empty: the service keeps no state between connections. Audio that you sent after the server started to close is not returned. For a live stream, continue with new audio on the new connection. For a file, send the file again.

## Next steps

See [Examples](/docs/voice/noise-suppression/websocket-streaming/examples) for complete Python and Node.js code that denoises a WAV file.
