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_TOKEN header, or as the authorization query 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 ready before 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's sample_rate in 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

The first message sent by the client to initiate a VIVA Voice Isolation connection.

FieldTypeRequiredDefaultDescription
modelstringno"VIVA-tel"Voice Isolation model ID.
sample_rate8000 | 16000no16000Sample rate in Hz (8000 or 16000).
suppression_levelnumberno100.0Noise suppression level (0-100, 0 = passthrough).
metadataobject | nullnonullUser-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)

The first response sent by the server to the client. Successful handshake response format.

FieldTypeRequiredDefaultDescription
typestringno"ready"Response type
request_idstring | nullnonullRequest ID if available
tracks_msarray of integer | nullnonullDelays in ms of the decoders producing partial results. Each partial word's src is one of these values.
metadataobject | nullnonullUser-provided metadata, echoed back. Omitted entirely when the service's ClientHello carries no metadata field.

ErrorResponse (error)

Error response schema following the standard format.

FieldTypeRequiredDefaultDescription
typestringno"error"Response type
request_idstring | nullnonullRequest ID if available
errorErrorInfoyesError information

ErrorInfo

Error information structure.

FieldTypeRequiredDefaultDescription
codestringyesError code identifier
status_codeintegeryesHTTP status code
messagestringyesHuman-readable error message
detailsobject | nullnonullOptional error details (e.g., {'hint': '...'})

Cleaned audio

After ready, the server sends the cleaned audio as binary messages.

Supported models

ModelNotes
VIVA-telDefault 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.codestatus_codeWhen
invalid_client_hello400sample_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_failed500The requested model is not available, or the session could not be started.
chunk_too_large413An audio message was larger than 65,536 bytes. Send 30 ms frames.
invalid_frame_duration400An 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.

Did this page help you?