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}")import { KugelAudio } from 'kugelaudio';
import {
KugelAudioError,
AuthenticationError,
RateLimitError,
InsufficientCreditsError,
ValidationError,
NotFoundError,
ConnectionError,
ServerRestartingError,
} from 'kugelaudio';
try {
const audio = await client.tts.generate({ text: 'Hello!', voiceId: 1071 });
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Invalid API key');
} else if (error instanceof RateLimitError) {
console.error('Rate limit exceeded, please wait');
} else if (error instanceof InsufficientCreditsError) {
console.error('Not enough credits, please top up');
} else if (error instanceof NotFoundError) {
console.error('Voice, model, or dictionary not found');
} else if (error instanceof ValidationError) {
console.error(`Invalid request: ${error.message}`);
} else if (error instanceof ServerRestartingError) {
console.error(`Server restarting, retry in ${error.retryAfter}s`);
} else if (error instanceof ConnectionError) {
console.error('Failed to connect to server');
} else if (error instanceof KugelAudioError) {
console.error(`API error: ${error.message}`);
}
}import com.kugelaudio.sdk.*;
try {
AudioResponse audio = client.tts().generate(
GenerateRequest.builder("Hello!").voiceId(1071).language("en").build()
);
} catch (AuthenticationException e) {
System.err.println("Invalid API key");
} catch (RateLimitException e) {
System.err.println("Rate limit exceeded, please wait");
} catch (InsufficientCreditsException e) {
System.err.println("Not enough credits, please top up");
} catch (NotFoundException e) {
System.err.println("Voice, model, or dictionary not found");
} catch (ValidationException e) {
System.err.println("Invalid request: " + e.getMessage());
} catch (ConnectionException e) {
System.err.println("Failed to connect to server");
} catch (KugelAudioException e) {
System.err.println("API error: " + e.getMessage());
}use kugelaudio::{Error, TtsRequest};
match client.tts().generate(&TtsRequest::new("Hello!").voice_id(1071)).await {
Ok(audio) => audio.save_wav("output.wav")?,
Err(Error::Authentication(_)) => eprintln!("Invalid API key"),
Err(Error::RateLimit(_)) => eprintln!("Rate limit exceeded, please wait"),
Err(Error::InsufficientCredits(_)) => eprintln!("Not enough credits, please top up"),
Err(Error::NotFound(_)) => eprintln!("Voice, model, or dictionary not found"),
Err(Error::Validation(e)) => eprintln!("Invalid request: {}", e.message),
Err(Error::ServerRestarting(e)) => {
eprintln!("Server restarting, retry in {:?}s", e.retry_after)
}
Err(Error::Connection(e)) => eprintln!("WebSocket/network error: {}", e.message),
Err(e) => eprintln!("API error: {e} (request id {:?})", e.request_id()),
}try {
kugelaudio::GenerateRequest request;
request.text = "Hello!";
request.voice_id = 1071;
kugelaudio::AudioResponse audio = client.tts().generate(request);
} catch (const kugelaudio::AuthenticationError&) {
std::cerr << "Invalid API key\n";
} catch (const kugelaudio::RateLimitError&) {
std::cerr << "Rate limit exceeded, please wait\n";
} catch (const kugelaudio::InsufficientCreditsError&) {
std::cerr << "Not enough credits, please top up\n";
} catch (const kugelaudio::NotFoundError&) {
std::cerr << "Voice, model, or dictionary not found\n";
} catch (const kugelaudio::ValidationError& e) {
std::cerr << "Invalid request: " << e.message() << '\n';
} catch (const kugelaudio::ServerRestartingError& e) {
std::cerr << "Server restarting, retry in " << e.retry_after().value_or(1) << "s\n";
} catch (const kugelaudio::ConnectionError& e) {
std::cerr << "WebSocket/network error: " << e.message() << '\n';
} catch (const kugelaudio::KugelAudioError& e) {
std::cerr << "API error: " << e.what() << '\n'; // what() includes the request id
}try {
await client.tts.generate(text: 'Hello!', voiceId: 1071);
} on AuthenticationException {
print('Invalid API key');
} on RateLimitException {
print('Rate limit exceeded, please wait');
} on InsufficientCreditsException {
print('Not enough credits, please top up');
} on NotFoundException {
print('Voice, model, or dictionary not found');
} on ValidationException catch (e) {
print('Invalid request: ${e.message}');
} on ServerRestartingException catch (e) {
print('Server restarting, retry in ${e.retryAfter}');
} on ConnectionException catch (e) {
print('WebSocket/network error: ${e.message}');
} on KugelAudioException catch (e) {
print('API error: ${e.message} (request ${e.requestId})');
}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, Dart | Rust | Raised when |
|---|---|---|---|
AuthenticationError | AuthenticationException | Error::Authentication | API key is missing, invalid, or revoked. |
RateLimitError | RateLimitException | Error::RateLimit | Request rate limit exceeded. |
InsufficientCreditsError | InsufficientCreditsException | Error::InsufficientCredits | The account/wallet has no remaining credits. |
NotFoundError | NotFoundException | Error::NotFound | A referenced voice, model, dictionary, or entry doesn't exist or isn't visible to the caller (HTTP 404). |
ValidationError | ValidationException | Error::Validation | The request was malformed or a parameter was out of range. |
ConnectionError | ConnectionException | Error::Connection | A 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. |
ServerRestartingError | ServerRestartingException (Dart only) | Error::ServerRestarting | Subclass 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. |
KugelAudioError | KugelAudioException | Error::Api | Any 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:
| SDK | HTTP status | Error code | Request ID | Suggested wait |
|---|---|---|---|---|
| Python | status_code | error_code | request_id | retry_after (seconds) |
| TypeScript | statusCode | errorCode (compare with the exported ErrorCodes map; it can be undefined, for example on network failures) | requestId | retryAfter (seconds) |
| Java | getStatusCode() | getErrorCode() | getRequestId() | getRetryAfter() (seconds) |
| Rust | status_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 |
| Dart | statusCode | errorCode (compare with ErrorCodes) | requestId | retryAfter (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.
type Region = 'eu' | 'us' | 'global';
interface KugelAudioOptions {
apiKey: string; // Required; an 'eu-' prefix selects the EU endpoint
isToken?: boolean; // Treat apiKey as a JWT token
orgId?: number; // Org to bill against (token auth)
region?: Region; // 'eu' selects the direct EU endpoint
apiUrl?: string; // Default: https://api.kugelaudio.com
ttsUrl?: string; // Default: same as apiUrl
timeout?: number; // Default: 60000 (ms)
keepalivePingInterval?: number | null; // Default: 20000 (ms); 0/null disables; Node only
}KugelAudioOptions.builder(apiKey) is listed in
Configuration; Region is the enum
Region.EU, Region.US, Region.GLOBAL.
KugelAudio::builder(api_key) is listed in
Configuration; Region is the enum
Region::Eu, Region::Us, Region::Global, and parses from "eu", "us" or
"global" with str::parse.
enum class Region { EU, US, Global };
enum class LogLevel { Debug, Info, Warning };
using LogHandler = std::function<void(LogLevel level, const std::string& message)>;
struct ClientOptions {
std::string api_key; // Required; an "eu-" prefix selects the EU endpoint
std::optional<std::string> api_url; // Default: https://api.kugelaudio.com
std::optional<std::string> tts_url; // Default: same as api_url
std::optional<Region> region; // Region::EU selects the direct EU endpoint
std::chrono::milliseconds timeout{std::chrono::seconds(60)};
std::optional<std::chrono::seconds> keepalive_ping_interval{std::chrono::seconds(20)};
std::optional<bool> telemetry; // Default: on for the hosted API
LogHandler log_handler; // Default: warnings to std::cerr
};kugelaudio::parse_region("eu") parses a region name.
The KugelAudio(...) constructor arguments are listed in
Configuration; Region is the enum
Region.eu, Region.us, Region.global, and Region.parse('eu') parses a name.
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: ...interface GenerateOptions {
text: string; // Required: Text to synthesize
modelId?: string; // Default: 'kugel-3.5'
voiceId?: number; // SDK-optional; required for successful synthesis
cfgScale?: number; // Guidance scale 1.2-2.5; default: 2.0
temperature?: number; // Sampling variance 0.0-1.0; omit for server default
maxNewTokens?: number; // Default: 2048
sampleRate?: number; // Default: 24000
outputFormat?: string; // 'pcm_24000' | 'ulaw_8000' | 'alaw_8000' | ...
normalize?: boolean; // Default: true - Enable text normalization
language?: string; // ISO 639-1 code for normalization (e.g., 'en', 'de')
wordTimestamps?: boolean; // Default: false - Request word-level timestamps
speed?: number; // Playback speed 0.8-1.2 (default 1.0); pitch-preserving
projectId?: number; // Project ID for dictionary lookup (all auth modes)
dictionaryIds?: number[]; // With projectId: omit = all active, [] = none,
// list = exact IDs (including inactive ones)
}GenerateRequest request = GenerateRequest.builder("Text to synthesize") // Required
.modelId("kugel-3.5")
.voiceId(1071) // Builder-optional; required for successful synthesis
.cfgScale(2.0) // Guidance scale 1.2-2.5
.temperature(0.5) // Sampling variance 0.0-1.0; omit for server default
.maxNewTokens(2048)
.sampleRate(24000)
.outputFormat("pcm_24000") // or "ulaw_8000", "alaw_8000", ...
.normalize(true)
.language("en") // ISO 639-1 code for normalization
.wordTimestamps(false)
.speed(1.0) // Playback speed 0.8-1.2; pitch-preserving
.projectId(42L) // Project whose dictionaries apply
.dictionaryIds(List.of(7)) // Exact selection; List.of() disables
.build();let request = TtsRequest::new("Text to synthesize") // Required
.model_id("kugel-3.5") // Default: "kugel-3.5"
.voice_id(1071) // Optional in the type; required for successful synthesis
.cfg_scale(2.0) // Guidance scale 1.2-2.5
.temperature(0.5) // Sampling variance 0.0-1.0; omit for server default
.max_new_tokens(2048)
.sample_rate(24000)
.output_format(OutputFormat::Pcm24000) // or OutputFormat::Ulaw8000, ...
.normalize(true)
.language("en") // ISO 639-1 code for normalization
.word_timestamps(false) // stream() only; generate() rejects true
.speed(1.0) // Playback speed 0.8-1.2; pitch-preserving
.project_id(42) // Project whose dictionaries apply
.dictionary_ids(vec![7]); // Exact selection; vec![] disablesstruct GenerateRequest {
std::string text; // Required: text to synthesize
std::string model_id = "kugel-3.5";
std::optional<std::int64_t> voice_id; // Required for successful synthesis
double cfg_scale = 2.0; // Guidance scale, clamped to 1.2-2.5
std::optional<double> temperature; // Unset: server default
int max_new_tokens = 2048;
int sample_rate = 24000;
std::optional<std::string> output_format; // "pcm_24000" | "ulaw_8000" | "alaw_8000" | ...
bool normalize = true;
std::optional<std::string> language; // ISO 639-1 code for normalization
bool word_timestamps = false; // stream() only; generate() rejects true
double speed = 1.0; // Playback speed 0.8-1.2; pitch-preserving
std::optional<std::vector<std::int64_t>> dictionary_ids; // Unset: all active; empty: none
std::optional<std::int64_t> project_id; // Project whose dictionaries apply
};// client.tts.generate(); stream() takes the same arguments plus wordTimestamps,
// reuseConnection and onWordTimestamps
Future<AudioResponse> generate({
required String text, // Required: text to synthesize
int? voiceId, // Nullable; required for successful synthesis
String modelId = 'kugel-3.5',
double cfgScale = 2.0, // Guidance scale 1.2-2.5
double? temperature, // null: server default
int maxNewTokens = 2048,
int sampleRate = 24000,
String? outputFormat, // OutputFormat.pcm24000 | OutputFormat.ulaw8000 | ...
bool normalize = true,
String? language, // ISO 639-1 code for normalization
double speed = 1.0, // Playback speed 0.8-1.2; pitch-preserving
List<int>? dictionaryIds, // null: all active; []: none
int? projectId, // Project whose dictionaries apply
})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]."""interface AudioChunk {
audio: string; // Base64-encoded bytes in `encoding`
encoding: 'pcm_s16le' | 'mulaw' | 'alaw'; // G.711 when outputFormat is set
index: number; // Chunk index (0-based)
sampleRate: number; // Requested sample rate (24000 by default)
samples: number; // Number of samples in chunk
}AudioChunk chunk;
byte[] bytes = chunk.getAudio(); // Raw bytes in chunk.getEncoding() format
String enc = chunk.getEncoding(); // "pcm_s16le", "mulaw", or "alaw"
int index = chunk.getIndex(); // 0-based chunk index
int rate = chunk.getSampleRate(); // 24000, or 8000 for G.711
int n = chunk.getSamples(); // Number of samples; G.711 uses 1 byte/sample
float[] f32 = chunk.toFloat32(); // PCM-only; throws for "mulaw"/"alaw"pub struct AudioChunk {
pub audio: Vec<u8>, // Raw bytes in `encoding`
pub index: u64, // Chunk index (0-based)
pub sample_rate: u32, // 24000 by default, 8000 for G.711
pub samples: u64, // Number of samples in chunk
pub encoding: AudioEncoding, // PcmS16le, Mulaw or Alaw
pub chunk_id: Option<u64>, // Server text chunk id, when sent
pub context_id: Option<String>, // Context of a multi-context session
}
// chunk.duration_seconds() -> f64
// chunk.to_f32() -> Result<Vec<f32>> (PCM16 only; fails for G.711)struct AudioChunk {
Bytes audio; // Raw bytes in `encoding`
std::string encoding = "pcm_s16le"; // "pcm_s16le", "mulaw" or "alaw"
std::int64_t index = 0; // Chunk index (0-based)
int sample_rate = 24000; // 8000 for G.711
std::int64_t samples = 0; // Number of samples in chunk
std::optional<std::int64_t> chunk_id; // Server text chunk id, when sent
std::optional<std::string> context_id; // Context of a multi-context session
double duration_seconds() const noexcept;
};class AudioChunk {
final Uint8List audio; // Raw bytes in `encoding`
final int index; // Chunk index (0-based)
final int sampleRate; // 24000 by default, 8000 for G.711
final int samples; // Number of samples in chunk
final String encoding; // AudioEncoding.pcmS16le, .mulaw or .alaw
final int? chunkId; // Server text chunk id, when sent
final String? contextId; // Context of a multi-context session
Duration get duration;
Float32List toFloat32(); // PCM16 only; throws StateError for G.711
}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."""interface AudioResponse {
audio: ArrayBuffer; // Complete bytes in the requested output format
sampleRate: number; // The sampleRate you passed (24000 by default); set it to match outputFormat
samples: number; // Total samples
durationMs: number; // Duration in milliseconds
generationMs: number; // Generation time in milliseconds
rtf: number; // Real-time factor
wordTimestamps: WordTimestamp[]; // Per-word timing (when wordTimestamps: true)
}
// What onFinal of a one-shot stream() receives
interface GenerationStats {
final: true;
chunks: number; // Number of chunks generated
totalSamples: number; // Total samples generated
durationMs: number; // Audio duration in ms
generationMs: number; // Generation time in ms
rtf: number; // Real-time factor
error?: string; // Error message when supplied by the final frame
usage?: SessionUsage; // Per-request usage (audio time + charge); undefined if not reported
}AudioResponse audio;
byte[] bytes = audio.getAudio(); // Raw bytes in audio.getEncoding() format
String enc = audio.getEncoding(); // "pcm_s16le", "mulaw", or "alaw"
int rate = audio.getSampleRate(); // 24000
int samples = audio.getTotalSamples();
double durationMs = audio.getDurationMs();
double genMs = audio.getGenerationMs();
double rtf = audio.getRtf(); // Real-time factor
List<WordTimestamp> timestamps = audio.getWordTimestamps(); // empty unless requested
SessionUsage usage = audio.getUsage(); // null if the server omitted usage
audio.saveWav(Path.of("output.wav")); // PCM-only; throws for "mulaw"/"alaw"
byte[] wav = audio.toWavBytes(); // PCM-only WAV bytes with 44-byte header
float[] f32 = audio.toFloat32(); // PCM-only normalised [-1.0, 1.0]pub struct AudioResponse {
pub audio: Vec<u8>, // Complete bytes in `encoding`
pub sample_rate: u32, // Sample rate of the returned audio
pub encoding: AudioEncoding, // PcmS16le, Mulaw or Alaw
pub samples: u64, // Total samples
pub duration_ms: Option<f64>, // From stream(); None for generate()
pub generation_ms: Option<f64>, // From stream(); None for generate()
pub rtf: Option<f64>, // From stream(); None for generate()
pub word_timestamps: Vec<WordTimestamp>, // When word_timestamps(true) on stream()
pub usage: Option<SessionUsage>, // From stream(); None for generate()
pub request_id: Option<String>, // x-request-id of generate()
}
// audio.duration_seconds() -> f64
// audio.to_f32() -> Result<Vec<f32>> (PCM16 only)
// audio.to_wav() -> Vec<u8> (a G.711 WAV for G.711 audio)
// audio.save_wav(path) -> Result<()>
// What StreamEvent::Final of a one-shot stream() carries
pub struct FinalStats {
pub chunks: Option<u64>,
pub total_samples: Option<u64>,
pub duration_ms: Option<f64>,
pub generation_ms: Option<f64>,
pub rtf: Option<f64>,
pub usage: Option<SessionUsage>,
pub raw: serde_json::Value, // The whole frame
// ... plus the streaming-session fields total_audio_seconds,
// total_text_chunks, total_audio_chunks and context_id
}struct AudioResponse {
Bytes audio; // Complete bytes in `encoding`
int sample_rate = 24000; // Sample rate of the returned audio
std::string encoding = "pcm_s16le"; // "pcm_s16le", "mulaw" or "alaw"
std::int64_t samples = 0; // Total samples
double duration_ms = 0.0;
double generation_ms = 0.0; // From stream(); 0 for generate()
double rtf = 0.0; // From stream(); 0 for generate()
std::vector<WordTimestamp> word_timestamps; // When word_timestamps is set on stream()
std::optional<SessionUsage> usage; // From stream(), when reported
std::optional<std::string> request_id; // x-request-id of generate()
bool interrupted = false; // True when cancel() ended the stream
double duration_seconds() const noexcept;
Bytes to_wav() const; // A G.711 WAV for G.711 audio
void save_wav(const std::filesystem::path& path) const;
};class AudioResponse {
final Uint8List audio; // Complete bytes in `encoding`
final int sampleRate; // Sample rate of the returned audio
final int samples; // Total samples
final String encoding; // 'pcm_s16le', 'mulaw' or 'alaw'
final double? generationMs; // From stream().collect(); null for generate()
final double? rtf; // From stream().collect(); null for generate()
final List<WordTimestamp> wordTimestamps; // When wordTimestamps: true on stream()
final SessionUsage? usage; // From stream().collect(); null for generate()
final String? requestId; // x-request-id of generate()
Duration get duration;
double get durationMs;
Float32List toFloat32(); // PCM16 only
Uint8List toWav(); // A G.711 WAV for G.711 audio
// saveWav(path) and saveRaw(path) come with package:kugelaudio/io.dart
}
// What `await stream.result` of a one-shot stream() returns
class GenerationStats {
final int chunks;
final int totalSamples;
final double durationMs;
final double generationMs;
final double rtf;
final SessionUsage? usage;
final Map<String, Object?> raw; // The whole frame
}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."""interface WordTimestamp {
word: string; // The word
startMs: number; // Start time in milliseconds
endMs: number; // End time in milliseconds
charStart: number; // Character start index in original text
charEnd: number; // Character end index in original text
score: number; // Compatibility field; currently always 1.0
}WordTimestamp ts;
String word = ts.getWord(); // The aligned word
long startMs = ts.getStartMs(); // Start time in milliseconds
long endMs = ts.getEndMs(); // End time in milliseconds
int charStart = ts.getCharStart(); // Start character offset in original text
int charEnd = ts.getCharEnd(); // End character offset in original text
double score = ts.getScore(); // Compatibility field; currently always 1.0pub struct WordTimestamp {
pub word: String, // The aligned word
pub start_ms: i64, // Start time in milliseconds
pub end_ms: i64, // End time in milliseconds
pub char_start: i64, // Start character offset in original text
pub char_end: i64, // End character offset in original text
pub score: f64, // Compatibility field; currently always 1.0
}
// ts.duration_ms() -> i64 (end_ms - start_ms)struct WordTimestamp {
std::string word; // The aligned word
std::int64_t start_ms = 0; // Start time in milliseconds
std::int64_t end_ms = 0; // End time in milliseconds
std::int64_t char_start = 0; // Start character offset in original text
std::int64_t char_end = 0; // End character offset in original text
double score = 1.0; // Compatibility field; currently always 1.0
std::int64_t duration_ms() const noexcept; // end_ms - start_ms
};class WordTimestamp {
final String word; // The aligned word
final int startMs; // Start time in milliseconds
final int endMs; // End time in milliseconds
final int charStart; // Start character offset in original text
final int charEnd; // End character offset in original text
final double score; // Compatibility field; currently always 1.0
int get durationMs; // endMs - startMs
}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)."""interface SessionUsage {
audioSeconds: number; // Audio generated (the unit we bill on)
costCents: number | null; // Actual charge in EUR cents; null if undetermined
currency?: string; // Currency of costCents ('eur'); set only when costCents is
characters?: number; // Input characters; omitted on multi-context per-context usage
modelId?: string; // Model that produced the audio
costAvailable: boolean; // true when an authoritative charge was returned
}SessionUsage usage;
double audioSeconds = usage.getAudioSeconds(); // Audio generated (always present)
Double costCents = usage.getCostCents(); // Actual charge in EUR cents, or null
String currency = usage.getCurrency(); // Currency of costCents
Integer characters = usage.getCharacters(); // Input characters
String modelId = usage.getModelId(); // Model that produced the audio
boolean costAvailable = usage.isCostAvailable(); // true when an authoritative charge was returnedpub struct SessionUsage {
pub audio_seconds: f64, // Audio generated (the unit we bill on)
pub cost_cents: Option<f64>, // Actual charge in EUR cents; None if undetermined
pub currency: Option<String>, // Currency of cost_cents
pub characters: Option<u64>, // Input characters
pub model_id: Option<String>, // Model that produced the audio
}
// usage.cost_available() -> bool (true when an authoritative charge was returned)struct SessionUsage {
double audio_seconds = 0.0; // Audio generated (the unit we bill on)
std::optional<double> cost_cents; // Actual charge in EUR cents; unset if undetermined
std::optional<std::string> currency; // Currency of cost_cents
std::optional<std::int64_t> characters; // Input characters
std::optional<std::string> model_id; // Model that produced the audio
bool cost_available() const noexcept; // true when an authoritative charge was returned
};class SessionUsage {
final double audioSeconds; // Audio generated (the unit we bill on)
final double? costCents; // Actual charge in EUR cents; null if undetermined
final String? currency; // Currency of costCents
final int? characters; // Input characters
final String? modelId; // Model that produced the audio
bool get costAvailable; // true when an authoritative charge was returned
}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 applyinterface StreamConfig {
voiceId?: number; // SDK-optional; required before synthesis
modelId?: string; // Default: 'kugel-3.5'
cfgScale?: number; // Guidance scale 1.2-2.5; default: 2.0
temperature?: number; // Sampling variance 0.0-1.0; omit for server default
maxNewTokens?: number;
sampleRate?: number;
outputFormat?: string; // Combined codec + rate token
flushTimeoutMs?: number;
maxBufferLength?: number;
normalize?: boolean;
language?: string; // ISO 639-1 code; default: the voice's primary language
wordTimestamps?: boolean;
chunkLengthSchedule?: number[]; // Accepted, no effect
autoMode?: boolean; // Accepted, no effect
speed?: number; // Playback speed 0.8-1.2 (default 1.0); pitch-preserving
projectId?: number; // Project whose dictionaries apply (see Dictionaries)
dictionaryIds?: number[]; // Exact selection; [] disables; non-empty requires projectId
}StreamConfig config = StreamConfig.builder()
.voiceId(1071) // Builder-optional; required before synthesis
.modelId("kugel-3.5")
.cfgScale(2.0)
.temperature(0.5)
.maxNewTokens(2048)
.sampleRate(24000)
.outputFormat("pcm_24000")
.normalize(true)
.language("en")
.wordTimestamps(false)
.flushTimeoutMs(500)
.speed(1.0)
.projectId(42L) // Project whose dictionaries apply
.dictionaryIds(List.of(7)) // Exact selection; List.of() disables
.build();
// .chunkLengthSchedule(...) and .autoMode(...) are accepted and have no effectlet config = StreamConfig::new(1071) // Sets voice_id; StreamConfig::default() has none
.model_id("kugel-3.5")
.cfg_scale(2.0)
.temperature(0.5) // Omit for server default
.max_new_tokens(2048)
.sample_rate(24000)
.output_format(OutputFormat::Pcm24000)
.flush_timeout_ms(500)
.max_buffer_length(1000)
.normalize(true)
.language("en")
.word_timestamps(false)
.speed(1.0)
.project_id(42) // Project whose dictionaries apply
.dictionary_ids(vec![7]); // Exact selection; vec![] disables
// .chunk_length_schedule(...) and .auto_mode(...) are accepted and have no effectstruct StreamConfig {
std::optional<std::int64_t> voice_id; // Required before synthesis
std::optional<std::string> model_id;
double cfg_scale = 2.0;
std::optional<double> temperature; // Unset: server default
int max_new_tokens = 2048;
int sample_rate = 24000;
std::optional<std::string> output_format; // Combined codec + rate token
int flush_timeout_ms = 500;
int max_buffer_length = 1000;
bool normalize = true;
std::optional<std::string> language;
bool word_timestamps = false;
std::optional<std::vector<int>> chunk_length_schedule; // Accepted, no effect
std::optional<bool> auto_mode; // Accepted, no effect
double speed = 1.0;
std::optional<std::vector<std::int64_t>> dictionary_ids; // Empty disables
std::optional<std::int64_t> project_id; // Project whose dictionaries apply
};StreamConfig({
int? voiceId, // Required before synthesis
String? modelId,
double cfgScale = 2.0,
double? temperature, // null: server default
int maxNewTokens = 2048,
int sampleRate = 24000,
String? outputFormat, // Combined codec + rate token
int flushTimeoutMs = 500,
int maxBufferLength = 1000,
bool normalize = true,
String? language,
bool wordTimestamps = false,
List<int>? chunkLengthSchedule, // Accepted, no effect
bool? autoMode, // Accepted, no effect
double speed = 1.0,
List<int>? dictionaryIds, // [] disables
int? projectId, // Project whose dictionaries apply
})
// config.copyWith(...) returns a changed copyIn 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(...).
Used with the one-shot client.tts.stream():
interface StreamCallbacks {
onOpen?: () => void;
onChunk?: (chunk: AudioChunk) => void;
onWordTimestamps?: (timestamps: WordTimestamp[]) => void;
onFinal?: (stats: GenerationStats) => void;
onError?: (error: Error) => void;
onClose?: () => void;
// A rolling deploy closed the socket before any audio arrived; the SDK
// re-issues the request once after error.retryAfter seconds
onServerRestart?: (error: ServerRestartingError) => void;
}Used with client.tts.streamingSession():
interface StreamingSessionCallbacks {
onChunk?: (chunk: AudioChunk) => void;
onChunkComplete?: (chunkId: number, audioSeconds: number, genMs: number) => void;
// End of audio for the turn (ElevenLabs isFinal equivalent); fires after
// the last audio frame, right before onSessionClosed. Not on barge-in.
onFinal?: (totalAudioSeconds: number, totalTextChunks: number, totalAudioChunks: number) => void;
onSessionClosed?: (totalAudioSeconds: number, totalTextChunks: number, totalAudioChunks: number) => void;
onGenerationStarted?: (chunkId: number, text: string) => void;
onWordTimestamps?: (timestamps: WordTimestamp[]) => void;
onInterrupted?: () => void;
onError?: (error: Error) => void;
// A rolling deploy closed the socket; the SDK reconnects after
// error.retryAfter seconds and replays the current turn
onServerRestart?: (error: ServerRestartingError) => void;
}Multi-context callbacks are under Multi-context types.
StreamCallbacks is used by both tts().stream(...) and streamingSession(...).
Only onChunk is required; the rest are default no-ops you override as needed:
| Method | Fires when |
|---|---|
onChunk(AudioChunk chunk) | An audio chunk arrives (required). |
onComplete(AudioResponse response) | The one-shot stream() request finishes. |
onGenerationStarted(int chunkId, String text) | The server starts generating a text segment (sessions). |
onChunkComplete(int chunkId, double audioSeconds, double genMs) | A flushed segment finishes (sessions). |
onSessionClosed(double totalAudioSeconds, int totalTextChunks, int totalAudioChunks) | The session is fully closed. |
onWordTimestamps(List<WordTimestamp> timestamps) | Word timestamps arrive (requires wordTimestamps(true)). |
onInterrupted() | The server acknowledges a barge-in (cancelCurrent()). |
onError(KugelAudioException error) | Any error occurs. |
MultiContextCallbacks are listed in the
Multi-context reference.
No callback interfaces: stream() yields StreamEvents and the session methods
yield AudioChunks. The only callbacks are on_word_timestamps on
StreamingSession (FnMut(&[WordTimestamp])) and on MultiContextSession
(FnMut(&str, &[WordTimestamp]), with the context ID).
Callbacks run on the thread that called the method; every field is an optional
std::function.
using ChunkCallback = std::function<void(const AudioChunk& chunk)>;
using WordTimestampsCallback = std::function<void(const std::vector<WordTimestamp>& stamps)>;
// tts().stream()
struct StreamCallbacks {
ChunkCallback on_audio; // Each audio chunk as it arrives
WordTimestampsCallback on_word_timestamps; // When word_timestamps is requested
};
// tts().streaming_session(); audio goes to the on_chunk argument of send() and flush()
struct StreamingSessionCallbacks {
WordTimestampsCallback on_word_timestamps;
std::function<void(const std::string& warning)> on_warning; // e.g. the idle auto-flush
};
// tts().multi_context_session()
struct MultiContextCallbacks {
std::function<void(const std::string& context_id, const std::vector<WordTimestamp>& stamps)>
on_word_timestamps;
};No callback interfaces: audio arrives on streams. tts.stream() returns a
Stream<AudioChunk> and takes one optional onWordTimestamps callback; a
StreamingSession exposes audio, wordTimestamps and warnings streams; a
MultiContextSession exposes audio and wordTimestamps (of
ContextWordTimestamps, with the context ID).
Multi-context types
The session is configured by the arguments of multi_context_session(...) and
yields AudioChunks per context; see the
Multi-context reference.
interface MultiContextConfig {
defaultVoiceId?: number;
sampleRate?: number;
outputFormat?: string;
cfgScale?: number;
temperature?: number;
maxNewTokens?: number;
normalize?: boolean;
language?: string;
projectId?: number;
dictionaryIds?: number[];
inactivityTimeout?: number;
}
interface MultiContextAudioChunk extends AudioChunk {
contextId: string; // which context this audio belongs to
}
interface MultiContextCallbacks {
onSessionStarted?: (sessionId: string) => void;
onContextCreated?: (contextId: string) => void;
onChunk?: (chunk: MultiContextAudioChunk) => void;
// All audio admitted before a flush has been delivered for this context
// (ElevenLabs is_final equivalent); also fires before a graceful
// onContextClosed.
onFinal?: (contextId: string) => void;
onContextClosed?: (contextId: string, usage?: SessionUsage) => void;
onContextTimeout?: (contextId: string) => void;
onSessionClosed?: (stats: Record<string, unknown>) => void;
onError?: (error: Error, contextId?: string) => void;
// A rolling deploy closed the socket; the SDK reconnects after
// error.retryAfter seconds and replays pending text
onServerRestart?: (error: ServerRestartingError) => void;
}MultiContextConfig, CreateContextOptions and MultiContextCallbacks are listed
in the Multi-context reference.
updateSettings(...) takes a SettingsUpdate and returns EffectiveSettings
(getCfgScale(), getTemperature(), getSpeed(), getMaxNewTokens(),
getLanguage(), getNormalize()), the same types as on a streaming session.
let config = MultiContextConfig::default()
.default_voice_id(1071) // Voice of contexts created without their own
.model_id("kugel-3.5")
.sample_rate(24000)
.output_format(OutputFormat::Pcm24000)
.cfg_scale(2.0)
.temperature(0.5)
.max_new_tokens(2048)
.normalize(true)
.language("en")
.project_id(42)
.dictionary_ids(vec![7])
.word_timestamps(false);Audio arrives as AudioChunks whose context_id names the context.
update_settings(...) takes a SettingsUpdate and returns GenerationSettings, the
same types as on a streaming session.
struct MultiContextConfig {
std::optional<std::int64_t> default_voice_id; // Voice of contexts created without their own
std::optional<std::string> model_id;
int sample_rate = 24000;
std::optional<std::string> output_format;
double cfg_scale = 2.0;
std::optional<double> temperature;
int max_new_tokens = 2048;
bool normalize = true;
std::optional<std::string> language;
std::optional<std::vector<std::int64_t>> dictionary_ids;
std::optional<std::int64_t> project_id;
bool word_timestamps = false;
std::optional<double> speed; // 0.8-1.2; unset uses the server default
};Audio arrives as AudioChunks whose context_id names the context.
update_settings(...) takes and returns GenerationSettings, the same type as on a
streaming session.
MultiContextConfig({
int? defaultVoiceId, // Voice of contexts created without their own
String? modelId,
int sampleRate = 24000,
String? outputFormat,
double cfgScale = 2.0,
double? temperature,
int maxNewTokens = 2048,
bool normalize = true,
String? language,
double speed = 1.0,
List<int>? dictionaryIds,
int? projectId,
bool wordTimestamps = false,
})
class ContextWordTimestamps {
final String contextId;
final List<WordTimestamp> timestamps;
}Audio arrives on session.audio as AudioChunks whose contextId names the
context. updateSettings(...) takes the same named arguments as on a streaming
session.
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)interface Model {
id: string; // e.g. 'kugel-3'
name: string; // Human-readable name
description: string; // Model description
parameters: string; // Parameter-count label (e.g. '7B')
maxInputLength: number; // Maximum input characters
sampleRate: number; // Output sample rate
}Model model;
String id = model.getId(); // e.g. 'kugel-3'
String name = model.getName();
String description = model.getDescription();
String parameters = model.getParameters(); // null for current models
int maxInput = model.getMaxInputLength();
int sampleRate = model.getSampleRate();pub struct Model {
pub id: String, // e.g. 'kugel-3'
pub name: String, // Human-readable name
pub description: Option<String>,
pub parameters: Option<String>, // Parameter-count label
pub max_input_length: Option<u32>, // Maximum input characters
pub sample_rate: Option<u32>, // Output sample rate
}struct Model {
std::string id; // e.g. 'kugel-3'
std::string name; // Human-readable name
std::string description;
std::string parameters; // Parameter-count label
int max_input_length = 5000; // Maximum input characters
int sample_rate = 24000; // Output sample rate
};class Model {
final String id; // e.g. 'kugel-3'
final String name; // Human-readable name
final String description;
final String parameters; // Parameter-count label
final int maxInputLength; // Maximum input characters (default 5000)
final int sampleRate; // 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 usedinterface VoiceListResponse {
voices: Voice[]; // Voices on this page
total: number; // Total number of matching voices
limit: number; // Page size used
offset: number; // Offset used
}VoiceListResponse result;
List<Voice> voices = result.getVoices(); // Voices on this page
int total = result.getTotal(); // Total matching voices
int limit = result.getLimit(); // Page size used
int offset = result.getOffset(); // Offset usedpub struct VoiceListResponse {
pub voices: Vec<Voice>, // Voices on this page
pub total: u64, // Total number of matching voices
pub limit: u64, // Page size used
pub offset: u64, // Offset used
}struct VoiceListResponse {
std::vector<Voice> voices; // Voices on this page
std::int64_t total = 0; // Total number of matching voices
std::int64_t limit = 0; // Page size used
std::int64_t offset = 0; // Offset used
};class VoiceListResponse {
final List<Voice> voices; // Voices on this page
final int total; // Total number of matching voices
final int limit; // Page size used
final int offset; // 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 = Falsetype VoiceCategory = 'premade' | 'cloned' | 'designed' | 'conversational' | 'narrative' | 'narrative_story' | 'characters';
type VoiceSex = 'male' | 'female' | 'neutral';
type VoiceAge = 'young' | 'middle_aged' | 'old';
type VoiceQuality = 'low' | 'mid' | 'high';
interface Voice {
id: number; // Voice ID
name: string; // Voice name
description?: string; // Description
category?: VoiceCategory;
sex?: VoiceSex;
age?: VoiceAge;
quality?: string;
supportedLanguages: string[]; // ['en', 'de', ...]
sampleText?: string; // Text used for sample generation
avatarUrl?: string; // Avatar image URL
sampleUrl?: string; // Sample audio URL
isPublic: boolean;
verified: boolean;
}Voice voice;
int id = voice.getId();
String name = voice.getName();
String sex = voice.getSex(); // 'male', 'female', 'neutral'
String quality = voice.getQuality(); // 'low', 'mid', 'high'
String sampleUrl = voice.getSampleUrl();
boolean isPublic = voice.isPublic();pub struct Voice {
pub id: i64, // Voice ID
pub handle: Option<String>,
pub name: String, // Voice name
pub description: Option<String>,
pub category: Option<VoiceCategory>, // see the enums below
pub sex: Option<VoiceSex>,
pub age: Option<VoiceAge>,
pub quality: Option<String>, // 'low', 'mid', 'high'
pub supported_languages: Vec<String>, // ['en', 'de', ...]
pub sample_text: Option<String>,
pub avatar_url: Option<String>, // Avatar image URL
pub sample_url: Option<String>, // Sample audio URL
pub is_public: bool,
pub verified: bool,
}struct Voice {
std::int64_t id = 0; // Voice ID
std::string name; // Voice name
std::optional<std::string> description;
std::optional<std::string> category; // see kugelaudio::voice_category
std::optional<std::string> sex; // 'male', 'female'
std::optional<std::string> age;
std::string quality = "mid"; // 'low', 'mid', 'high'
std::vector<std::string> supported_languages; // {"en", "de", ...}
std::optional<std::string> sample_text;
std::optional<std::string> avatar_url; // Avatar image URL
std::optional<std::string> sample_url; // Sample audio URL
bool is_public = false;
bool verified = false;
};class Voice {
final int id; // Voice ID
final String name; // Voice name
final String? description;
final VoiceCategory? category; // see the enums below
final VoiceSex? sex;
final VoiceAge? age;
final String quality; // 'low', 'mid', 'high'
final List<String> supportedLanguages; // ['en', 'de', ...]
final String? sampleText;
final String? avatarUrl; // Avatar image URL
final String? sampleUrl; // Sample audio URL
final bool isPublic;
final bool verified;
}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 = ""interface VoiceDetail {
id: number;
name: string;
description: string;
generativeVoiceDescription: string;
supportedLanguages: string[];
category: string;
age?: string;
sex?: string;
quality: string; // 'low' | 'mid' | 'high'
isPublic: boolean;
verified: boolean;
pendingVerification: boolean;
sampleUrl?: string;
avatarUrl?: string;
sampleText: string;
}VoiceDetail voice;
int id = voice.getId();
String name = voice.getName();
String sex = voice.getSex();
String quality = voice.getQuality();
String sampleUrl = voice.getSampleUrl();
boolean isPublic = voice.isPublic(); // meaningful on create/update/publish only
List<VoiceReference> refs = voice.getReferences(); // legacy; normally empty
String language = voice.getLanguage(); // legacy; normally nullpub struct VoiceDetail {
pub id: i64,
pub handle: Option<String>,
pub name: String,
pub description: Option<String>,
pub generative_voice_description: Option<String>,
pub supported_languages: Vec<String>,
pub category: Option<VoiceCategory>,
pub age: Option<VoiceAge>,
pub sex: Option<VoiceSex>,
pub quality: Option<String>, // 'low', 'mid', 'high'
pub is_public: bool,
pub verified: bool,
pub pending_verification: bool,
pub sample_url: Option<String>,
pub avatar_url: Option<String>,
pub sample_text: Option<String>,
}struct VoiceDetail {
std::int64_t id = 0;
std::string name;
std::string description;
std::string generative_voice_description;
std::vector<std::string> supported_languages;
std::optional<std::string> category;
std::optional<std::string> age;
std::optional<std::string> sex;
std::string quality = "mid"; // 'low', 'mid', 'high'
bool is_public = false;
bool verified = false;
bool pending_verification = false;
std::optional<std::string> sample_url;
std::optional<std::string> avatar_url;
std::string sample_text;
};class VoiceDetail {
final int id;
final String name;
final String description;
final String generativeVoiceDescription;
final List<String> supportedLanguages;
final VoiceCategory? category;
final VoiceAge? age;
final VoiceSex? sex;
final String quality; // 'low', 'mid', 'high'
final bool isPublic;
final bool verified;
final bool pendingVerification;
final String? sampleUrl;
final String? avatarUrl;
final String sampleText;
}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 = Falseinterface VoiceReference {
id: number;
voiceId: number;
name: string;
referenceText: string;
s3Path: string;
audioUrl?: string;
isGenerated: boolean;
}VoiceReference ref;
int id = ref.getId();
String referenceText = ref.getReferenceText();
String filename = ref.getFilename(); // legacy; not populated by the current APIpub struct VoiceReference {
pub id: i64,
pub voice_id: i64,
pub name: Option<String>,
pub reference_text: Option<String>,
pub s3_path: Option<String>,
pub audio_url: Option<String>,
pub is_generated: bool,
}struct VoiceReference {
std::int64_t id = 0;
std::int64_t voice_id = 0;
std::string name;
std::string reference_text;
std::string s3_path;
std::optional<std::string> audio_url;
bool is_generated = false;
};class VoiceReference {
final int id;
final int voiceId;
final String name;
final String referenceText;
final String s3Path;
final String? audioUrl;
final bool isGenerated;
}Enums. category, sex, and age on voice models are string enums defined in
kugelaudio.models:
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, OLDThese 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.
VoiceCategory and VoiceAge are legacy SDK declarations, not the API's
current write-value set. The API can return newer category strings and uses
middle_age (not the declared middle_aged); the TypeScript mapper does not
transform those values, so runtime data can fall outside these unions. Use
the Voice API reference for accepted
create/update values.
The create and update options:
interface CreateVoiceOptions {
name: string;
sex: string;
description?: string;
category?: string;
age?: string;
quality?: string;
supportedLanguages?: string[];
isPublic?: boolean;
sampleText?: string;
referenceFiles?: Array<File | Blob>;
}
interface UpdateVoiceOptions {
name?: string;
description?: string;
category?: string;
age?: string;
sex?: string;
quality?: string;
supportedLanguages?: string[];
isPublic?: boolean;
sampleText?: string;
}The SDK serializes isPublic from UpdateVoiceOptions, but the current API
ignores that field on voice updates. Use voices.publish() to make a voice
public. The API's generative_voice_description write field is not exposed
by CreateVoiceOptions or UpdateVoiceOptions.
The current Java Voice/VoiceDetail models still bind legacy language
and embedded references fields. The API now returns
supported_languages and exposes references through
voices().listReferences(...), so getLanguage() is normally null and
VoiceDetail.getReferences() is normally empty. VoiceReference.getFilename()
likewise binds a legacy field; use getId() and getReferenceText(), or the
raw Voice API when you need the current name, path, and URL fields.
isPublic() is only meaningful on create, update, and publish
responses: list() and get() return the catalog shape, which has no
is_public, so it is always false there.
Enums. category, sex, and age are string enums; as_str() returns the
API value, and a value this SDK version does not know is kept as Other(String):
use kugelaudio::{VoiceAge, VoiceCategory, VoiceSex};
pub enum VoiceCategory {
Conversational, NarrativeStory, CharactersAnimation, SocialMedia,
EntertainmentTv, Advertisement, InformativeEducational, // sent by the API
Cloned, // returned for a voice with no category set
Premade, Designed, Narrative, Characters, // older values, never returned today
Other(String),
}
pub enum VoiceSex { Male, Female, Neutral, Other(String) }
pub enum VoiceAge { Young, MiddleAged, MiddleAge, Old, Other(String) }The API returns middle_age (VoiceAge::MiddleAge). VoiceCreate and
VoiceUpdate take plain strings; use the Voice API
reference for accepted write values.
Categories. category, sex, and age are plain strings, so a value added on
the server never breaks parsing. kugelaudio::voice_category holds the category
constants the API accepts and returns: kConversational, kNarrativeStory,
kCharactersAnimation, kSocialMedia, kEntertainmentTv, kAdvertisement,
kInformativeEducational, and kCloned for a voice with no category set. Ages are
"young", "middle_age" and "old"; qualities "low", "mid" and "high".
Enums. category, sex, and age are enums whose value is the API string:
enum VoiceCategory {
conversational, narrativeStory, charactersAnimation, socialMedia,
entertainmentTv, advertisement, informativeEducational, // sent by the API
cloned, // returned for a voice with no category set
premade, designed, narrative, characters, // older values, never returned today
}
enum VoiceSex { male, female, neutral }
enum VoiceAge { young, middleAged, middleAge, old }The API returns middle_age (VoiceAge.middleAge). A category this SDK version
does not know maps to cloned and logs a warning. voices.create() and
voices.update() take plain strings; 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: intinterface Dictionary {
id: number;
projectId: number;
name: string;
description?: string;
language?: string;
isActive: boolean;
createdAt: string;
updatedAt: string;
}
interface DictionaryEntry {
id: number;
dictionaryId: number;
word: string;
replacement: string;
ipa?: string;
caseSensitive: boolean;
createdAt: string;
updatedAt: string;
}
interface DictionaryEntryInput { // payload for add() / replaceAll()
word: string;
replacement: string;
ipa?: string;
caseSensitive?: boolean;
}
interface DictionaryEntryListResponse { // returned by entries.list()
entries: DictionaryEntry[];
total: number;
limit: number;
offset: number;
}
interface BulkReplaceResult { // returned by entries.replaceAll()
upserted: number;
deleted: number;
total: number;
}
interface CreateDictionaryOptions { name: string; description?: string; language?: string; }
interface UpdateDictionaryOptions { name?: string; description?: string; language?: string; isActive?: boolean; }
interface UpdateDictionaryEntryOptions { word?: string; replacement?: string; ipa?: string; caseSensitive?: boolean; }Dictionary d; // getId(), getProjectId(), getName(), getDescription(),
// getLanguage(), isActive(), getCreatedAt(), getUpdatedAt()
DictionaryEntry e; // getId(), getDictionaryId(), getWord(), getReplacement(),
// getIpa(), isCaseSensitive(), getCreatedAt(), getUpdatedAt()
// Payload for add() / replaceAll()
new DictionaryEntryInput("Postgres", "post-gres");
new DictionaryEntryInput("Postgres", "post-gres", "ˈpoʊstɡrɛs", false); // ipa, caseSensitive
DictionaryEntryListResponse page; // getEntries(), getTotal(), getLimit(), getOffset()
BulkReplaceResult result; // getUpserted(), getDeleted(), getTotal()pub struct Dictionary {
pub id: i64,
pub project_id: i64,
pub name: String,
pub description: Option<String>,
pub language: Option<String>,
pub is_active: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
pub struct DictionaryEntry {
pub id: i64,
pub dictionary_id: i64,
pub word: String,
pub replacement: String,
pub ipa: Option<String>,
pub case_sensitive: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
// Payload for entries().add()
NewDictionaryEntry::new("Postgres", "post-gres").ipa("ˈpoʊstɡrɛs").case_sensitive(false);
// One item of entries().replace_all()
DictionaryEntryInput::new("Postgres", "post-gres").ipa("ˈpoʊstɡrɛs");
// Updates: only set fields change
DictionaryCreate::new("Brand names").description("...").language("en");
DictionaryUpdate::new().name("...").is_active(false);
DictionaryEntryUpdate::new().replacement("postgres").case_sensitive(true);
pub struct DictionaryEntryList { pub entries: Vec<DictionaryEntry>, pub total: u64, pub limit: u64, pub offset: u64 }
pub struct BulkReplaceResult { pub upserted: u64, pub deleted: u64, pub total: u64 }struct Dictionary {
std::int64_t id = 0;
std::int64_t project_id = 0;
std::string name;
std::optional<std::string> description;
std::optional<std::string> language;
bool is_active = true;
std::optional<std::string> created_at;
std::optional<std::string> updated_at;
};
struct DictionaryEntry {
std::int64_t id = 0;
std::int64_t dictionary_id = 0;
std::string word;
std::string replacement;
std::optional<std::string> ipa;
bool case_sensitive = false;
std::optional<std::string> created_at;
std::optional<std::string> updated_at;
};
struct DictionaryEntryInput { // payload for add() / replace_all()
std::string word;
std::string replacement;
std::optional<std::string> ipa;
bool case_sensitive = false;
};
struct DictionaryEntryList { std::vector<DictionaryEntry> entries; std::int64_t total, limit, offset; };
struct BulkReplaceResult { std::int64_t upserted, deleted, total; };
// DictionaryCreate { name, description, language }
// DictionaryUpdate { name, description, language, is_active } (unset fields are kept)
// DictionaryEntryUpdate { word, replacement, ipa, case_sensitive } (unset fields are kept)
// DictionaryEntryListOptions { search, limit = 100, offset = 0 }class Dictionary {
final int id;
final int projectId;
final String name;
final String? description;
final String? language;
final bool isActive;
final String? createdAt;
final String? updatedAt;
}
class DictionaryEntry {
final int id;
final int dictionaryId;
final String word;
final String replacement;
final String? ipa;
final bool caseSensitive;
final String? createdAt;
final String? updatedAt;
}
// One item of entries.replaceAll(); entries.add() takes the same named arguments
const DictionaryEntryInput(word: 'Postgres', replacement: 'post-gres', ipa: 'ˈpoʊstɡrɛs');
class DictionaryEntryList { // returned by entries.list()
final List<DictionaryEntry> entries;
final int total;
final int limit;
final int offset;
}
class BulkReplaceResult { // returned by entries.replaceAll()
final int upserted;
final int deleted;
final int total;
}Next steps
- Quickstart: install and first generation
- Streaming: where
StreamConfig, the callbacks andSessionUsageare used - API Reference: the raw HTTP and WebSocket contract