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

# Speech Enhancement

> Remove background noise, or keep one speaker's voice, over REST or real-time WebSocket

Speech enhancement cleans up recorded or live speech. It does one of two tasks:

| Task                        | What it does                                                                                                                     | Use it when                                                                                                        |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `noise_removal`             | Removes background noise and keeps the speech.                                                                                   | One person is talking over traffic, fans, keyboard clicks or room noise.                                           |
| `target_speaker_extraction` | Keeps one person's voice and removes every other voice and sound. You identify that person with a short sample of them speaking. | Several people are audible (a call center floor, a meeting, a TV in the background) and you only want one of them. |

The output is always mono 16-bit PCM at 24 kHz, the same length as your input.

<Warning>
  Speech enhancement is available in preview. It is not yet intended for
  production workloads, and the interface may still change.
</Warning>

All requests need an API key; see [Authentication](/api-reference/authentication).
Your organization also needs access to `clarity-1`.

## Billing

Enhancement is billed per second of input audio processed, rounded up to the
next whole second, with a one-second minimum per request. A stream that
disconnects early is billed for the audio processed up to that point. A request
that fails with an error is not billed.

## Python SDK

```python theme={null}
from kugelaudio import KugelAudio, load_audio

client = KugelAudio(api_key="YOUR_API_KEY")

audio = load_audio("call.wav")
result = await client.enhance.generate(audio, model="clarity-1")
result.save("clean.wav")
```

To keep one voice, pass `speaker=load_audio("speaker.wav")`. `load_audio` takes
a path or WAV bytes in any [supported format](#enhance-a-recording). The result
has `.audio` (PCM16 bytes), `.sample_rate` (`24000`), `.duration` (seconds),
`.wav` (WAV bytes) and `.save(path)`.

### Real-time

```python theme={null}
from kugelaudio import KugelAudio, load_audio_stream

client = KugelAudio(api_key="YOUR_API_KEY")

audio = load_audio_stream("call.wav")
async for chunk in client.enhance.stream(audio, model="clarity-1"):
    print(len(chunk), "bytes")  # enhanced 16-bit mono PCM, 24 kHz, as it arrives
```

* Add `speaker=load_audio("speaker.wav")` to keep one voice.
* `load_audio_stream` takes a path or WAV bytes: a 16-bit WAV (stereo is mixed
  down).
* Any iterable or async iterable of 16-bit mono PCM chunks also works with
  `sample_rate=`.

For plain scripts without `asyncio`, `generate_sync(...)` and `stream_sync(...)`
take the same arguments.

## Enhance a recording

<ParamField path="POST" method="/v1/audio/enhance" />

Send a `multipart/form-data` upload.

<ParamField body="model" type="string" required>
  `clarity-1`. Any other value is rejected.
</ParamField>

<ParamField body="file" type="file" required>
  The recording, as a WAV file. At most 300 seconds, and at most 64 MB for the whole request.
</ParamField>

<ParamField body="task" type="string" default="noise_removal">
  `noise_removal` or `target_speaker_extraction`.
</ParamField>

<ParamField body="speaker" type="file">
  Required for `target_speaker_extraction`, rejected for `noise_removal`. A WAV
  sample of the person to keep, 2–8 seconds long. See
  [A good speaker sample](#a-good-speaker-sample).
</ParamField>

**Accepted WAV formats** for both `file` and `speaker`: 16-, 24- or 32-bit
integer PCM, or 32-bit float; mono or stereo (stereo is mixed down to mono);
sample rates from 8 kHz to 48 kHz.

**Response** `200`: an `audio/wav` body, mono 16-bit PCM at 24 kHz, the same
duration as the input. The `X-Audio-Duration-Seconds` header carries that
duration.

<CodeGroup>
  ```bash cURL — remove noise theme={null}
  curl https://api.kugelaudio.com/v1/audio/enhance \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -F "model=clarity-1" \
    -F "file=@call.wav" \
    -F "task=noise_removal" \
    -o call-clean.wav
  ```

  ```bash cURL — keep one voice theme={null}
  curl https://api.kugelaudio.com/v1/audio/enhance \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -F "model=clarity-1" \
    -F "file=@meeting.wav" \
    -F "task=target_speaker_extraction" \
    -F "speaker=@speaker.wav" \
    -o one-voice.wav
  ```
</CodeGroup>

### Errors

Errors return the standard JSON error body; see
[Error Codes](/api-reference/errors).

| Status | `error_code`           | Trigger                                                                                                                                                                                                                                                                    |
| -----: | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|  `400` | `VALIDATION_ERROR`     | Unreadable or unsupported WAV, empty audio, input over 300 seconds, missing or unsupported `model`, sample rate outside 8–48 kHz, unknown `task`, `speaker` missing for `target_speaker_extraction` or sent with `noise_removal`, or a speaker sample outside 2–8 seconds. |
|  `401` | `UNAUTHORIZED`         | Missing or invalid API key.                                                                                                                                                                                                                                                |
|  `402` | `INSUFFICIENT_CREDITS` | The organization's balance is spent.                                                                                                                                                                                                                                       |
|  `403` | `UNAUTHORIZED`         | Speech enhancement is not enabled for your organization.                                                                                                                                                                                                                   |
|  `413` | `VALIDATION_ERROR`     | The request is larger than 64 MB. Send 16-bit mono audio, or a shorter recording.                                                                                                                                                                                          |
|  `429` | `RATE_LIMITED`         | Your organization's rate or concurrency limit is reached, or enhancement is at capacity. Retry after the `Retry-After` seconds.                                                                                                                                            |
|  `502` | `INTERNAL_ERROR`       | Enhancement failed while processing the request. Retry.                                                                                                                                                                                                                    |
|  `503` | `MODEL_UNAVAILABLE`    | Enhancement is temporarily unavailable, or billing is not set up for your organization. Retry after `Retry-After` when present; otherwise contact support.                                                                                                                 |

## Enhance live audio

<ParamField path="WS" method="/v1/audio/enhance/stream" />

Connect to `wss://api.kugelaudio.com/v1/audio/enhance/stream` and authenticate
in the handshake, as for any WebSocket
([Authentication](/api-reference/authentication#websocket-connections)).
A missing or invalid key is refused during the handshake with HTTP `401`, before
the connection opens. Access, credits and rate limits are checked after the
`config` message; a refusal arrives as an `error` message followed by a close
code (below).

1. **Client → text** `config`:
   ```json theme={null}
   {"type": "config", "model": "clarity-1", "task": "noise_removal", "sample_rate_hz": 16000, "encoding": "pcm_s16le"}
   ```
   `model` is required and must be `clarity-1`. `task` is `noise_removal` or
   `target_speaker_extraction`. `sample_rate_hz`
   is 8000–48000. For `target_speaker_extraction`, add `"speaker_wav_b64"`: the
   speaker sample as a base64-encoded WAV file, 2–8 seconds.
2. **Server → text** `{"type": "ready", "sample_rate_hz": 24000, "encoding": "pcm_s16le"}`.
3. **Client → binary**: mono 16-bit little-endian PCM at your declared rate,
   at most 1 second of audio per message.
4. **Server → binary**: enhanced mono 16-bit little-endian PCM at 24 kHz, sent
   as it is produced.
5. **Client → text** `{"type": "end"}`. The server sends the remaining audio,
   then `{"type": "done", "duration_s": <seconds>}`, and closes with `1000`.
   The total output matches your input's duration.

```python theme={null}
import asyncio
import base64
import json
import os
import wave

import websockets

URL = "wss://api.kugelaudio.com/v1/audio/enhance/stream"

async def main():
    headers = {"Authorization": f"Bearer {os.environ['KUGELAUDIO_API_KEY']}"}
    speaker = open("speaker.wav", "rb").read()
    out = bytearray()

    with wave.open("meeting.wav", "rb") as src:  # mono, 16-bit
        rate = src.getframerate()
        async with websockets.connect(URL, additional_headers=headers) as ws:
            await ws.send(json.dumps({
                "type": "config",
                "model": "clarity-1",
                "task": "target_speaker_extraction",
                "sample_rate_hz": rate,
                "encoding": "pcm_s16le",
                "speaker_wav_b64": base64.b64encode(speaker).decode(),
            }))
            ready = json.loads(await ws.recv())
            if ready["type"] != "ready":
                raise RuntimeError(ready)

            async def send():
                while chunk := src.readframes(rate // 5):  # 200 ms per message
                    await ws.send(chunk)
                await ws.send(json.dumps({"type": "end"}))

            sender = asyncio.create_task(send())
            async for message in ws:
                if isinstance(message, bytes):
                    out += message  # mono PCM16 at 24 kHz
                    continue
                event = json.loads(message)
                if event["type"] == "error":
                    raise RuntimeError(event)
                if event["type"] == "done":
                    break
            await sender

    with wave.open("one-voice.wav", "wb") as dst:
        dst.setnchannels(1)
        dst.setsampwidth(2)
        dst.setframerate(24000)
        dst.writeframes(bytes(out))

asyncio.run(main())
```

### Errors and close codes

On an error the server sends
`{"type": "error", "code": "<error_code>", "message": "..."}` and then closes
the connection.

|   Code | Meaning                                                                                      |
| -----: | -------------------------------------------------------------------------------------------- |
| `1000` | Normal close after `done`.                                                                   |
| `1011` | Enhancement failed mid-stream.                                                               |
| `4400` | Invalid `config` or audio message.                                                           |
| `4401` | Missing or invalid API key.                                                                  |
| `4402` | The organization's balance is spent.                                                         |
| `4403` | Speech enhancement is not enabled for your organization.                                     |
| `4408` | No audio received for 30 seconds.                                                            |
| `4429` | Rate or concurrency limit reached, or enhancement is at capacity. Retry after a short delay. |
| `4503` | Enhancement is temporarily unavailable. Retry after a short delay.                           |

## A good speaker sample

`target_speaker_extraction` only works as well as the sample you give it.

* **2–8 seconds** of speech. Shorter or longer samples are rejected.
* **That person alone.** No one else talking, not even briefly.
* **Little background noise.** A quiet room is best; clean it up with
  `noise_removal` first if you only have a noisy recording.
