Skip to content
KugelAudioDocs

Types & Errors

Errors, data models, callbacks, and enums exported by the SDKs

Errors

from kugelaudio import KugelAudio
from kugelaudio.exceptions import (
    KugelAudioError,
    AuthenticationError,
    RateLimitError,
    InsufficientCreditsError,
    ValidationError,
    NotFoundError,
    ServerRestartingError,
)
# 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 ServerRestartingError as e:
    print(f"Server restarting, retry in {e.retry_after}s")
except KugelAudioConnectionError as e:
    print(f"WebSocket/network error: {e}")
except KugelAudioError as e:
    print(f"API error: {e}")

Every error extends the SDK's base error (KugelAudioError in Python, TypeScript and C++, the unchecked KugelAudioException in Java, KugelAudioException in Dart). Rust returns kugelaudio::Result<T> with one kugelaudio::Error variant per category:

Python, TypeScript, C++Java, DartRustRaised when
AuthenticationErrorAuthenticationExceptionError::AuthenticationAPI key is missing, invalid, or revoked.
RateLimitErrorRateLimitExceptionError::RateLimitRequest rate limit exceeded.
InsufficientCreditsErrorInsufficientCreditsExceptionError::InsufficientCreditsThe account/wallet has no remaining credits.
NotFoundErrorNotFoundExceptionError::NotFoundA referenced voice, model, dictionary, or entry doesn't exist or isn't visible to the caller (HTTP 404).
ValidationErrorValidationExceptionError::ValidationThe request was malformed or a parameter was out of range.
ConnectionErrorConnectionExceptionError::ConnectionA WebSocket or network error occurred, or the service answered HTTP 503 / MODEL_UNAVAILABLE (retry shortly). In Java also a request that ran past the client's timeout. Python exports it from the package root as KugelAudioConnectionError to avoid shadowing the built-in.
ServerRestartingErrorServerRestartingException (Dart only)Error::ServerRestartingSubclass of the connection error (in Rust its own variant). A rolling deploy closed the WebSocket (close code 1012 or 1013) and the SDK could not replay the turn without repeating audio. Reconnect and resend the in-flight turn after the suggested wait. Java has no equivalent; see Reconnecting after a server restart.
KugelAudioErrorKugelAudioExceptionError::ApiAny other API error.

Every error carries the HTTP status, the server's machine-readable error code (when known), the request ID (quote it to support), and the suggested wait when the server or SDK suggests one:

SDKHTTP statusError codeRequest IDSuggested wait
Pythonstatus_codeerror_coderequest_idretry_after (seconds)
TypeScriptstatusCodeerrorCode (compare with the exported ErrorCodes map; it can be undefined, for example on network failures)requestIdretryAfter (seconds)
JavagetStatusCode()getErrorCode()getRequestId()getRetryAfter() (seconds)
Ruststatus_code()error_code()request_id()retry_after() (seconds); the same fields are public on the variant's ApiError
C++status_code()error_code()request_id()retry_after() (seconds); each a std::optional
DartstatusCodeerrorCode (compare with ErrorCodes)requestIdretryAfter (a Duration)

The TypeScript package also exports WsCloseCodes for matching WebSocket close codes. When the TypeScript SDK can replay a turn transparently after a rolling deploy, it calls the onServerRestart callback instead of failing; see Callbacks.

Client options

The KugelAudio(...) constructor arguments are listed in Configuration.

Generation options

# client.tts.generate(); generate_async(), stream() and stream_async() take the same arguments
def generate(
    self,
    text: str,                          # Required: text to synthesize
    model_id: str = "kugel-3.5",
    voice_id: int | None = None,        # SDK-optional; required for successful synthesis
    cfg_scale: float = 2.0,             # Guidance scale 1.2-2.5
    temperature: float | None = None,   # None: server default
    max_new_tokens: int = 2048,
    sample_rate: int = 24000,
    output_format: str | None = None,   # "pcm_24000" | "ulaw_8000" | "alaw_8000" | ...
    normalize: bool = True,
    language: str | None = None,        # ISO 639-1 code for normalization
    word_timestamps: bool = False,
    speed: float = 1.0,                 # Playback speed 0.8-1.2; pitch-preserving
    dictionary_ids: list[int] | None = None,
    project_id: int | None = None,
) -> AudioResponse: ...

What each parameter does is in Generation parameters. Normalizing without a language may cause incorrect normalizations; always set the language when you know it.

AudioChunk

A single audio chunk from streaming:

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 from generation:

class AudioResponse:
    audio: bytes                          # Complete bytes in the requested output format
    sample_rate: int                      # Sample rate of the returned audio
    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 the audio as raw G.711 bytes instead (the Rust, C++ and Dart WAV helpers write a G.711 WAV). Unlike Python and Java, the TypeScript AudioResponse has no usage; per-request usage is available from stream() as GenerationStats.usage. In Rust, C++ and Dart, generate() reports no usage either; read it from stream().

WordTimestamp

Word-level time alignment for generated audio:

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: per streaming session, per multi-context context, and per one-shot request (where each is reported is listed in Per-session usage).

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)."""

StreamConfig

Configuration of a streaming session:

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  # accepted, no effect
    auto_mode: bool | None = None                   # accepted, no effect
    speed: float = 1.0
    dictionary_ids: list[int] | None = None  # requires project_id
    project_id: int | None = None  # project whose dictionaries apply

In Python the session factories accept the common generation fields directly. Set the advanced fields (output_format, max_buffer_length) as keywords on session.update_config(...) before the first send; passing a whole StreamConfig there replaces every field, including ones you did not set. Why the chunk-schedule fields have no effect is in Server-side chunking.

Callbacks

Python returns audio from iterators, Rust and Dart from streams; TypeScript, Java and C++ deliver it to callbacks.

No callback interfaces: stream(), stream_async() and the session methods yield the audio. The only callbacks are on_word_timestamps on streaming_session(...) and multi_context_session(...).

Multi-context types

The session is configured by the arguments of multi_context_session(...) and yields AudioChunks per context; see the Multi-context reference.

Model

TTS model information, returned by the models listing:

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)

Voice types

VoiceListResponse, the paginated response of the voice listing:

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, the items of the listing. The listing does not send the sample text, public or verified fields, so they always hold their defaults here:

class Voice:
    id: int                                # Voice ID
    name: str                              # Voice name
    description: str | None = None
    category: VoiceCategory | None = None  # see Enums below
    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, returned by get, create, update, and publish. get receives the listing shape, so the generative voice description, public, verified, pending-verification and sample-text fields hold their defaults there; create, update, and publish populate them.

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, reference audio metadata:

class VoiceReference:
    id: int
    voice_id: int
    name: str = ""
    reference_text: str = ""
    s3_path: str = ""
    audio_url: str | None = None
    is_generated: bool = False

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

Python
from kugelaudio.models import VoiceAge, VoiceCategory, VoiceSex

class VoiceCategory(str, Enum):
    NARRATIVE_STORY, CONVERSATIONAL, CHARACTERS_ANIMATION, SOCIAL_MEDIA,
    ENTERTAINMENT_TV, ADVERTISEMENT, INFORMATIVE_EDUCATIONAL,  # sent by the API
    CLONED,  # returned for a voice with no category set
    PREMADE, DESIGNED, NARRATIVE, CHARACTERS  # older values, never returned today

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

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

These enums describe response values. The API returns middle_age for VoiceAge. A category this SDK version does not know deserializes as CLONED and logs a warning. Use the Voice API reference for accepted write values.

Dictionary types

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

Next steps

  • Quickstart: install and first generation
  • Streaming: where StreamConfig, the callbacks and SessionUsage are used
  • API Reference: the raw HTTP and WebSocket contract