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

# Types & Errors

> Exceptions, data models, and enums exported by the SDK

## Error Handling

```python theme={null}
from kugelaudio import KugelAudio
from kugelaudio.exceptions import (
    KugelAudioError,
    AuthenticationError,
    RateLimitError,
    InsufficientCreditsError,
    ValidationError,
    NotFoundError,
)
# ConnectionError is exported from the package root as KugelAudioConnectionError
from kugelaudio import KugelAudioConnectionError

try:
    audio = client.tts.generate(text="Hello!", voice_id=1071)
except AuthenticationError:
    print("Invalid API key")
except RateLimitError:
    print("Rate limit exceeded, please wait")
except InsufficientCreditsError:
    print("Not enough credits, please top up")
except NotFoundError:
    print("Voice, model, or dictionary not found")
except ValidationError as e:
    print(f"Invalid request: {e}")
except KugelAudioConnectionError as e:
    print(f"WebSocket/network error: {e}")
except KugelAudioError as e:
    print(f"API error: {e}")
```

All exceptions inherit from `KugelAudioError`:

| Exception                  | Raised when                                                                                                                        |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `AuthenticationError`      | API key is missing, invalid, or revoked.                                                                                           |
| `RateLimitError`           | Request rate limit exceeded.                                                                                                       |
| `InsufficientCreditsError` | The account/wallet has no remaining credits.                                                                                       |
| `NotFoundError`            | A referenced voice, model, dictionary, or entry doesn't exist or isn't visible to the caller (HTTP 404).                           |
| `ValidationError`          | The request was malformed or a parameter was out of range.                                                                         |
| `ConnectionError`          | A WebSocket/network error occurred. Exported from the package root as `KugelAudioConnectionError` to avoid shadowing the built-in. |

## Data Models

All models are importable from `kugelaudio` (e.g. `from kugelaudio import AudioChunk, StreamConfig`).

### AudioChunk

Represents a single audio chunk from streaming:

```python theme={null}
class AudioChunk:
    audio: bytes          # Raw bytes in the requested output format
    index: int            # Chunk index (0-based)
    sample_rate: int      # Requested sample rate (24000 by default)
    samples: int          # Number of samples in chunk

    @property
    def duration_seconds(self) -> float:
        """Duration of this chunk in seconds."""

    def to_float32(self) -> list[float]:
        """Convert PCM16 output to float32 samples in [-1.0, 1.0]."""
```

### AudioResponse

Complete audio response from generation:

```python theme={null}
class AudioResponse:
    audio: bytes                          # Complete bytes in the requested output format
    sample_rate: int                      # Requested sample rate (24000 by default)
    samples: int                          # Total samples
    duration_ms: float                    # Duration in milliseconds
    generation_ms: float                  # Generation time in milliseconds
    rtf: float                            # Real-time factor
    word_timestamps: list[WordTimestamp]  # Per-word timing (when word_timestamps=True)
    usage: SessionUsage | None            # Per-request usage (audio time + charge); None if not reported

    @property
    def duration_seconds(self) -> float:
        """Duration in seconds."""

    def to_float32(self) -> list[float]:
        """Convert PCM16 output to float32 samples in [-1.0, 1.0]."""

    def save(self, path: str, format: str = "wav") -> None:
        """Save audio to a file. format is 'wav' or 'raw' (headerless PCM)."""

    def to_wav_bytes(self) -> bytes:
        """Wrap PCM16 output in a WAV header."""
```

The conversion and WAV helpers assume PCM16 output. When requesting
`ulaw_8000` or `alaw_8000`, consume `audio` as raw G.711 bytes instead.

### WordTimestamp

Word-level time alignment for a generated audio chunk:

```python theme={null}
class WordTimestamp:
    word: str          # The aligned word
    start_ms: int      # Start time in milliseconds (relative to chunk)
    end_ms: int        # End time in milliseconds (relative to chunk)
    char_start: int    # Start character offset in original text
    char_end: int      # End character offset in original text
    score: float       # Compatibility field; currently always 1.0

    @property
    def duration_ms(self) -> int:
        """end_ms - start_ms."""
```

### SessionUsage

Per-conversation usage for billing your own customers. Available on
`StreamingSession.last_usage` (per session), `MultiContextSession.usage_for(...)`
(per context), and `AudioResponse.usage` (per `generate()` request).

```python theme={null}
class SessionUsage:
    audio_seconds: float          # Audio generated (the unit we bill on)
    cost_cents: float | None      # Actual charge in EUR cents; None if undetermined
    currency: str | None          # Currency of cost_cents ("eur"); set only when cost_cents is
    characters: int | None        # Input characters; omitted on multi-context per-context usage
    model_id: str | None          # Model that produced the audio

    @property
    def cost_available(self) -> bool:
        """True when an authoritative charge was returned (cost_cents is not None)."""
```

<Note>
  `cost_cents` is `None` (and `cost_available` is `False`) when the charge
  cannot be determined at session end — e.g. a transient billing error or an
  internal session. It is never a misleading `0`. `audio_seconds` is always
  reported, so you can still reconcile from the audio you received.
</Note>

### Model

TTS model information (returned by `client.models.list()`):

```python theme={null}
class Model:
    id: str                   # e.g. 'kugel-3'
    name: str                 # Human-readable name
    description: str          # Model description
    parameters: str           # Parameter-count label (e.g. '7B')
    max_input_length: int     # Maximum input characters (default 5000)
    sample_rate: int          # Output sample rate (default 24000)
```

### StreamConfig

Configuration object for streaming sessions. The session factories accept the
common generation fields directly. Use `StreamConfig` with
`session.update_config()` before the first send when setting advanced fields
such as `output_format`, `max_buffer_length`, `chunk_length_schedule`, or
`auto_mode`.

```python theme={null}
class StreamConfig:
    voice_id: int | None = None  # SDK-optional; required before synthesis
    model_id: str | None = None
    cfg_scale: float = 2.0
    output_format: str | None = None  # e.g. "pcm_24000", "ulaw_8000", "alaw_8000"
    temperature: float | None = None
    max_new_tokens: int = 2048
    sample_rate: int = 24000
    flush_timeout_ms: int = 500
    max_buffer_length: int = 1000
    normalize: bool = True
    language: str | None = None
    word_timestamps: bool = False
    chunk_length_schedule: list[int] | None = None  # default [5, 80, 150, 250]
    auto_mode: bool | None = None
    speed: float = 1.0
    dictionary_ids: list[int] | None = None
```

### Dictionary, DictionaryEntry & results

```python theme={null}
class Dictionary:
    id: int
    project_id: int
    name: str
    description: str | None = None
    language: str | None = None
    is_active: bool = True
    created_at: str | None = None
    updated_at: str | None = None

class DictionaryEntry:
    id: int
    dictionary_id: int
    word: str
    replacement: str
    ipa: str | None = None
    case_sensitive: bool = False
    created_at: str | None = None
    updated_at: str | None = None

class DictionaryEntryList:      # paginated response from entries.list()
    entries: list[DictionaryEntry]
    total: int
    limit: int
    offset: int

class BulkReplaceResult:        # returned by entries.replace_all()
    upserted: int
    deleted: int
    total: int
```

### Enums

`category`, `sex`, and `age` on voice models are string enums defined in
`kugelaudio.models`:

```python theme={null}
from kugelaudio.models import VoiceAge, VoiceCategory, VoiceSex

class VoiceCategory(str, Enum):
    PREMADE, CLONED, DESIGNED, CONVERSATIONAL, NARRATIVE, NARRATIVE_STORY, CHARACTERS

class VoiceSex(str, Enum):
    MALE, FEMALE, NEUTRAL

class VoiceAge(str, Enum):
    YOUNG, MIDDLE_AGED, MIDDLE_AGE, OLD
```

<Note>
  These are the SDK's legacy response enums, not the API's current create/update
  validation set. In particular, the API returns `middle_age` and may return
  newer categories not declared by `VoiceCategory`; an unrecognized category
  deserializes as `CLONED`. Use the [Voice API
  reference](/api-reference/endpoints/voices) for accepted write values.
</Note>

### VoiceListResponse

Paginated response from `voices.list()`:

```python theme={null}
class VoiceListResponse:
    voices: List[Voice]   # Voices on this page
    total: int            # Total number of matching voices
    limit: int            # Page size used
    offset: int           # Offset used
```

### Voice

Voice information (items in `voices.list().voices`):

```python theme={null}
class Voice:
    id: int                                # Voice ID
    name: str                              # Voice name
    description: str | None = None
    category: VoiceCategory | None = None  # see Enums
    sex: VoiceSex | None = None
    age: VoiceAge | None = None
    quality: str = "mid"
    supported_languages: list[str] = []    # ['en', 'de', ...]
    sample_text: str | None = None
    avatar_url: str | None = None          # Avatar image URL
    sample_url: str | None = None          # Sample audio URL
    is_public: bool = False
    verified: bool = False
```

### VoiceDetail

Extended voice information returned by `get`, `create`, `update`, and `publish`:

```python theme={null}
class VoiceDetail:
    id: int
    name: str
    description: str = ""
    generative_voice_description: str = ""
    supported_languages: list[str] = []
    category: VoiceCategory | None = None
    age: VoiceAge | None = None
    sex: VoiceSex | None = None
    quality: str = "mid"                   # 'low', 'mid', 'high'
    is_public: bool = False
    verified: bool = False
    pending_verification: bool = False
    sample_url: str | None = None
    avatar_url: str | None = None
    sample_text: str = ""
```

### VoiceReference

Voice reference audio metadata:

```python theme={null}
class VoiceReference:
    id: int
    voice_id: int
    name: str = ""
    reference_text: str = ""
    s3_path: str = ""
    audio_url: str | None = None
    is_generated: bool = False
```

## Next steps

* [Quickstart](/sdks/python/quickstart) — install and first generation
* [Streaming](/sdks/python/streaming) — where `StreamConfig` and `SessionUsage` are used
