Voice Isolation API
Stream noisy audio to the Krisp Voice Isolation WebSocket endpoint and get it back with background voices and noise removed, in real time.
Stream noisy audio to Krisp over a WebSocket and get back the same audio with background noise and voices removed, frame by frame, in realtime. Typical uses are cleaning up telephony and contact-center audio before it reaches a listener, a recorder or a speech recognizer.
See Authentication for how to generate a session token, and the API overview for the session protocol shared by all endpoints.
Connecting
- URL:
wss://streaming.krisp.ai/voice-isolation - Auth: a session token generated from your API key, sent as the
Authorization: api-key SESSION_TOKENheader, or as theauthorizationquery parameter if headers can't be set. Do not send your API key (master token) on the WebSocket. See Authentication for how to generate a session token. - First message: the client hello as a JSON text message, within 15 seconds of connecting. Wait for
readybefore sending audio. - Audio: after
ready, send the audio as binary messages only. Do not send text messages after the hello. The Krisp example client sends 16-bit mono PCM at the hello'ssample_ratein 30 ms frames (960 bytes at 16 kHz). Each message can be at most 65,536 bytes. - Output: the cleaned audio comes back as binary messages. The example client expects each returned frame to be the same size as the frame it sent, and saves them as 16-bit mono PCM at the same sample rate.
- Finishing: close the WebSocket.
Frame size: each audio message must be exactly 30 ms of audio (480 bytes at 8 kHz, 960 bytes at 16 kHz). Any other size ends the session with invalid_frame_duration. If the last frame of your audio is shorter, pad it with silence.
Client hello
VIVAVIClientHello
VIVAVIClientHelloThe first message sent by the client to initiate a VIVA Voice Isolation connection.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
model | string | no | "VIVA-tel" | Voice Isolation model ID. |
sample_rate | 8000 | 16000 | no | 16000 | Sample rate in Hz (8000 or 16000). |
suppression_level | number | no | 100.0 | Noise suppression level (0-100, 0 = passthrough). |
metadata | object | null | no | null | User-provided metadata (max 4KB when JSON-encoded). |
metadata is echoed back in ready when set.
Example:
{
"model": "VIVA-tel",
"sample_rate": 16000,
"suppression_level": 100,
"metadata": {"call_id": "abc-123"}
}Server messages
GatewayServerHello (ready)
GatewayServerHello (ready)The first response sent by the server to the client. Successful handshake response format.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type | string | no | "ready" | Response type |
request_id | string | null | no | null | Request ID if available |
tracks_ms | array of integer | null | no | null | Delays in ms of the decoders producing partial results. Each partial word's src is one of these values. |
metadata | object | null | no | null | User-provided metadata, echoed back. Omitted entirely when the service's ClientHello carries no metadata field. |
ErrorResponse (error)
ErrorResponse (error)Error response schema following the standard format.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type | string | no | "error" | Response type |
request_id | string | null | no | null | Request ID if available |
error | ErrorInfo | yes | Error information |
ErrorInfo
ErrorInfoError information structure.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
code | string | yes | Error code identifier | |
status_code | integer | yes | HTTP status code | |
message | string | yes | Human-readable error message | |
details | object | null | no | null | Optional error details (e.g., {'hint': '...'}) |
Cleaned audio
After ready, the server sends the cleaned audio as binary messages.
Supported models
| Model | Notes |
|---|---|
VIVA-tel | Default model, used when model is omitted. |
If you request a model that is not available, the session fails to start with upstream_initialization_failed.
Errors
Besides the common errors, these are the most frequent on /voice-isolation:
error.code | status_code | When |
|---|---|---|
invalid_client_hello | 400 | sample_rate is not 8000 or 16000, suppression_level is outside 0 to 100, or metadata is larger than 4 KB. details.errors names the field. |
upstream_initialization_failed | 500 | The requested model is not available, or the session could not be started. |
chunk_too_large | 413 | An audio message was larger than 65,536 bytes. Send 30 ms frames. |
invalid_frame_duration | 400 | An audio message was not exactly 30 ms. Sent during the session, after ready; the connection is then closed with close code 1008. details has expected_frame_ms, received_frame_ms and sample_rate. Not retryable: fix the frame size and reconnect. |
{
"type": "error",
"request_id": "fbaa55b8-9130-4b16-a780-a384bbdf4130",
"error": {
"code": "invalid_frame_duration",
"status_code": 400,
"message": "Expected 30 ms at 16 kHz, received 40 ms at 16 kHz.",
"details": {
"expected_frame_ms": 30,
"received_frame_ms": 40,
"sample_rate": 16000,
"retryable": false
}
}
}Example
Streams a 16 kHz, mono, 16-bit PCM WAV file in exactly 30 ms frames and writes the cleaned audio to cleaned.wav. Requires pip install "websockets>=14".
import asyncio
import json
import sys
import wave
import websockets
SESSION_TOKEN = "YOUR_SESSION_TOKEN" # see the Authentication page
URL = "wss://streaming.krisp.ai/voice-isolation"
SAMPLE_RATE = 16000
FRAME_MS = 30
async def main(path: str) -> None:
headers = {"Authorization": f"api-key {SESSION_TOKEN}"}
async with websockets.connect(URL, additional_headers=headers) as ws:
await ws.send(json.dumps({
"model": "VIVA-tel",
"sample_rate": SAMPLE_RATE,
"suppression_level": 100,
}))
hello = json.loads(await ws.recv())
if hello["type"] != "ready":
raise RuntimeError(hello)
async def send_audio() -> None:
with wave.open(path, "rb") as wf: # 16 kHz, mono, 16-bit PCM
frames_per_chunk = SAMPLE_RATE * FRAME_MS // 1000
frame_bytes = frames_per_chunk * 2 # 16-bit samples
while chunk := wf.readframes(frames_per_chunk):
# Every message must be exactly 30 ms: pad the last one with silence.
await ws.send(chunk.ljust(frame_bytes, b"\x00"))
await asyncio.sleep(FRAME_MS / 1000) # realtime pace
await asyncio.sleep(1) # let the last frames arrive
await ws.close()
sender = asyncio.create_task(send_audio())
with wave.open("cleaned.wav", "wb") as out:
out.setnchannels(1)
out.setsampwidth(2)
out.setframerate(SAMPLE_RATE)
async for message in ws:
if isinstance(message, bytes):
out.writeframes(message)
else:
print(message)
await sender
asyncio.run(main(sys.argv[1]))Changelog
- 2026-10-01: Authentication now uses a session token; see Authentication.
- 2026-10-01: Page created: client hello, binary audio flow, model
VIVA-tel.
Updated about 14 hours ago

