Quickstart
Install the KugelAudio SDK for Python, TypeScript/JavaScript, Java, Rust, C++ or Dart and generate your first audio
KugelAudio ships official SDKs for Python, TypeScript/JavaScript, Java, Rust, C++ and Dart. Every page of this reference shows each language in its own tab; pick your language once and every code block follows it.
| SDK | Runtime | Source (MIT, issues and pull requests welcome) |
|---|---|---|
| Python: Pythonic interface with synchronous and asynchronous APIs | Python 3.10 or newer | Kugelaudio/python-sdk |
| TypeScript/JavaScript: type-safe interface for Node.js and browsers | Node.js 18 or newer, or a modern browser | Kugelaudio/js-sdk |
| Java: type-safe builder interface | Java 17 or newer | Kugelaudio/java-sdk |
Rust: async client on Tokio, streams as futures::Stream | Rust 1.89 or newer | Kugelaudio/rust-sdk |
| C++: C++17 library with blocking calls and callbacks | A C++17 compiler, CMake 3.20 or newer, libcurl | Kugelaudio/cpp-sdk |
| Dart: one package for Dart and Flutter, including Flutter web | Dart 3.4 or newer | Kugelaudio/dart-sdk |
Installation
pip install kugelaudioOr with uv (recommended):
uv add kugelaudionpm install kugelaudioOr with yarn/pnpm:
yarn add kugelaudio
# or
pnpm add kugelaudioThe ws WebSocket package is installed with it, so Node needs nothing extra.
TypeScript types ship inside the package. There is no @types/kugelaudio to
install, and no extra configuration is required.
Module systems. The SDK ships both an ESM and a CommonJS build, each with its own type declarations, so it works from either module system without a bundler or transpiler:
import { KugelAudio } from 'kugelaudio';
const client = new KugelAudio({ apiKey: process.env.KUGELAUDIO_API_KEY! });const { KugelAudio } = require('kugelaudio');
const client = new KugelAudio({ apiKey: process.env.KUGELAUDIO_API_KEY });Use named imports. The package intentionally has no default export, so
import KugelAudio from 'kugelaudio' is a type error.
Types resolve correctly under every TypeScript moduleResolution setting
(node, node16, nodenext, and bundler) for the main entry point and for
the kugelaudio/livekit subpath.
Add the dependency to your pom.xml:
<dependency>
<groupId>com.kugelaudio</groupId>
<artifactId>kugelaudio</artifactId>
<version>3.1.0</version>
</dependency>Or with Gradle:
implementation 'com.kugelaudio:kugelaudio:3.1.0'Add the crate and a Tokio runtime:
cargo add kugelaudio
cargo add tokio --features macros,rt-multi-thread
cargo add futures-util # StreamExt, for the streaming examplesEvery call is async and needs a Tokio runtime. TLS goes through rustls, so no
OpenSSL is needed.
Fetch the library with CMake FetchContent:
include(FetchContent)
FetchContent_Declare(kugelaudio
GIT_REPOSITORY https://github.com/Kugelaudio/cpp-sdk.git
GIT_TAG v0.1.0)
FetchContent_MakeAvailable(kugelaudio)
target_link_libraries(my_app PRIVATE kugelaudio::kugelaudio)libcurl must be installed (for example apt install libcurl4-openssl-dev; macOS
ships it). IXWebSocket and nlohmann/json are taken from your system when CMake finds
them, and downloaded at pinned versions otherwise.
Or install it with vcpkg, using the overlay port in the repository's ports/
directory, and find the installed package:
vcpkg install kugelaudio --overlay-ports=/path/to/cpp-sdk/portsfind_package(kugelaudio CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE kugelaudio::kugelaudio)Include the umbrella header <kugelaudio/kugelaudio.hpp>.
dart pub add kugelaudio # Dart
flutter pub add kugelaudio # FlutterThe package is pure Dart, so it runs on Android, iOS, macOS, Windows, Linux, the web
and servers. File helpers that need dart:io live in package:kugelaudio/io.dart.
Generate your first audio
Create an API key at app.kugelaudio.com/settings/api-keys
and keep it out of your code, for example in the KUGELAUDIO_API_KEY environment
variable (see Authentication). Only Java's
KugelAudio.fromEnv() reads KUGELAUDIO_API_KEY for you; the other SDKs do not read
the environment on their own, so pass the key explicitly.
import os
from kugelaudio import KugelAudio
# Initialize the client
client = KugelAudio(api_key=os.environ["KUGELAUDIO_API_KEY"])
# Generate speech
audio = client.tts.generate(
text="Hello, world!",
model_id="kugel-3.5",
voice_id=1071,
)
# Save to file
audio.save("output.wav")import { writeFileSync } from 'node:fs';
import { KugelAudio, createWavFile } from 'kugelaudio';
// Initialize the client
const client = new KugelAudio({ apiKey: process.env.KUGELAUDIO_API_KEY! });
try {
// Generate speech
const audio = await client.tts.generate({
text: 'Hello, world!',
modelId: 'kugel-3.5',
voiceId: 1071,
});
// audio.audio is raw PCM16; wrap it in a WAV header and save it
writeFileSync('output.wav', Buffer.from(createWavFile(audio.audio, audio.sampleRate)));
console.log(`Saved ${audio.durationMs}ms of audio to output.wav`);
} finally {
// The client keeps a pooled WebSocket open; close it so Node can exit
client.close();
}import com.kugelaudio.sdk.KugelAudio;
import com.kugelaudio.sdk.GenerateRequest;
import com.kugelaudio.sdk.AudioResponse;
import java.nio.file.Path;
public class HelloKugelAudio {
public static void main(String[] args) throws Exception {
// Reads KUGELAUDIO_API_KEY; or new KugelAudio(KugelAudioOptions.builder(key).build())
KugelAudio client = KugelAudio.fromEnv();
AudioResponse audio = client.tts().generate(
GenerateRequest.builder("Hello, world!")
.modelId("kugel-3.5")
.voiceId(1071)
.language("en")
.build()
);
audio.saveWav(Path.of("output.wav")); // throws IOException
client.close();
}
}use kugelaudio::{KugelAudio, TtsRequest};
#[tokio::main]
async fn main() -> kugelaudio::Result<()> {
// Initialize the client
let client = KugelAudio::new(std::env::var("KUGELAUDIO_API_KEY").unwrap_or_default())?;
// Generate speech
let request = TtsRequest::new("Hello, world!")
.model_id("kugel-3.5")
.voice_id(1071)
.language("en");
let audio = client.tts().generate(&request).await?;
// Save to file
audio.save_wav("output.wav")?;
// Close the pooled WebSocket and deliver pending diagnostics
client.close().await;
Ok(())
}#include <cstdlib>
#include <kugelaudio/kugelaudio.hpp>
int main() {
const char* key = std::getenv("KUGELAUDIO_API_KEY");
if (key == nullptr) return 1;
// Initialize the client
kugelaudio::Client client{key};
// Generate speech
kugelaudio::GenerateRequest request;
request.text = "Hello, world!";
request.model_id = "kugel-3.5";
request.voice_id = 1071;
request.language = "en";
kugelaudio::AudioResponse audio = client.tts().generate(request);
// Save to file
audio.save_wav("output.wav");
} // The destructor closes the clientimport 'dart:io';
import 'package:kugelaudio/io.dart';
Future<void> main() async {
// Initialize the client
final client = KugelAudio(apiKey: Platform.environment['KUGELAUDIO_API_KEY']!);
// Generate speech
final audio = await client.tts.generate(
text: 'Hello, world!',
modelId: 'kugel-3.5',
voiceId: 1071,
language: 'en',
);
// Save to file (io.dart; in Flutter web use audio.toWav() bytes instead)
await audio.saveWav('output.wav');
await client.close();
}In a browser, the TypeScript SDK plays the audio instead of saving it: see Playing audio in the browser.
Pre-connecting for low latency
For latency-sensitive applications, pre-establish the WebSocket connection at startup to keep the handshake out of your first TTS request. Without pre-connecting, the first TTS request includes the WebSocket connection setup; later requests reuse the connection. See Latency for typical numbers.
In the examples below, play_audio / playAudio stands for your own playback
function; the SDKs do not ship one.
Async applications (recommended):
import asyncio
import os
from kugelaudio import AudioChunk, KugelAudio
async def main():
# Create a pre-connected client (handshake happens here)
client = await KugelAudio.create(api_key=os.environ["KUGELAUDIO_API_KEY"])
# First request is now fast: no handshake on the hot path
async for item in client.tts.stream_async("Hello, world!", voice_id=1071):
if isinstance(item, AudioChunk):
play_audio(item.audio)
await client.aclose()
asyncio.run(main())Sync applications. For synchronous code that needs connection reuse, keep a streaming session open across sends:
import os
from kugelaudio import KugelAudio
client = KugelAudio(api_key=os.environ["KUGELAUDIO_API_KEY"])
with client.tts.streaming_session_sync(voice_id=1071) as session:
for chunk in session.send("Hello, world!"):
play_audio(chunk.audio)
for chunk in session.flush():
play_audio(chunk.audio)Factory method (recommended):
import { KugelAudio } from 'kugelaudio';
// Create a pre-connected client (handshake happens here)
const client = await KugelAudio.create({ apiKey: process.env.KUGELAUDIO_API_KEY! });
// First request is now fast: no handshake on the hot path
await client.tts.stream(
{ text: 'Hello, world!', modelId: 'kugel-3.5', voiceId: 1071 },
{ onChunk: (chunk) => playAudio(chunk.audio) }
);Manual connection:
import { KugelAudio } from 'kugelaudio';
// Initialize client
const client = new KugelAudio({ apiKey: process.env.KUGELAUDIO_API_KEY! });
// Pre-connect at startup (handshake happens here)
await client.connect();
// Check connection status
console.log(`Connected: ${client.isConnected()}`);
// First request is now fast
await client.tts.stream(
{ text: 'Hello, world!', voiceId: 1071 },
{ onChunk: (chunk) => playAudio(chunk.audio) }
);Pre-connecting moves the connection overhead to application startup. Call
client.close() when you are done so the process can exit.
By default, new KugelAudio(options) immediately starts a WebSocket connection in
the background (autoConnect = true), so the handshake is absorbed at client
construction rather than on the first request.
// Connection starts in background automatically (autoConnect = true by default)
KugelAudio client = new KugelAudio(
KugelAudioOptions.builder("your_api_key").build()
);
// If you need to guarantee the connection is ready before the first request:
client.connect();
System.out.println("Connected: " + client.isConnected());
// Or use the blocking factory method:
KugelAudio connectedClient = KugelAudio.createConnected(
KugelAudioOptions.builder("your_api_key").build()
);use futures_util::StreamExt;
use kugelaudio::{KugelAudio, StreamEvent, TtsRequest};
let client = KugelAudio::new("your_api_key")?;
// Pre-connect at startup (handshake happens here); None opens it for kugel-3.5
client.tts().connect(None).await?;
println!("Connected: {}", client.tts().is_connected());
// First request is now fast: it reuses the pooled socket
let mut stream = client
.tts()
.stream(&TtsRequest::new("Hello, world!").voice_id(1071))
.await?;
while let Some(event) = stream.next().await {
if let StreamEvent::Audio(chunk) = event? {
play_audio(&chunk.audio);
}
}kugelaudio::Client client{"your_api_key"};
// Pre-connect at startup (blocks until the handshake is done)
client.connect();
std::cout << "Connected: " << client.is_connected() << '\n';
// First request is now fast: it reuses the pooled socket
kugelaudio::GenerateRequest request;
request.text = "Hello, world!";
request.voice_id = 1071;
kugelaudio::StreamCallbacks callbacks;
callbacks.on_audio = [](const kugelaudio::AudioChunk& chunk) { play_audio(chunk.audio); };
client.tts().stream(request, callbacks);Factory method (recommended):
// Create a pre-connected client (handshake happens here)
final client = await KugelAudio.create(apiKey: 'your_api_key');
// First request is now fast: no handshake on the hot path
await for (final chunk in client.tts.stream(text: 'Hello, world!', voiceId: 1071)) {
playAudio(chunk.audio);
}Manual connection:
final client = KugelAudio(apiKey: 'your_api_key');
await client.tts.connect();
print('Connected: ${client.tts.isConnected}');Complete example
List the models and voices, generate audio and report its timing:
import os
from kugelaudio import KugelAudio
# Initialize client
client = KugelAudio(api_key=os.environ["KUGELAUDIO_API_KEY"])
# List available models
print("Available Models:")
for model in client.models.list():
print(f" - {model.id}: {model.name}")
# List available voices
print("\nAvailable Voices:")
result = client.voices.list(limit=5)
for voice in result.voices:
print(f" - {voice.id}: {voice.name}")
# Generate audio
print("\nGenerating audio...")
audio = client.tts.generate(
text="Welcome to KugelAudio. This is an example of high-quality text-to-speech synthesis.",
model_id="kugel-3.5",
voice_id=1071,
)
print(f"Generated {audio.duration_seconds:.2f}s of audio in {audio.generation_ms:.0f}ms")
print(f"Real-time factor: {audio.rtf:.2f}x")
# Save to file
audio.save("example.wav")
print("Saved to example.wav")
# Close client
client.close()import { KugelAudio, base64ToArrayBuffer } from 'kugelaudio';
async function main() {
// Initialize client
const client = new KugelAudio({ apiKey: process.env.KUGELAUDIO_API_KEY! });
try {
// List available models
console.log('Available Models:');
const models = await client.models.list();
for (const model of models) {
console.log(` - ${model.id}: ${model.name}`);
}
// List available voices
console.log('\nAvailable Voices:');
const { voices } = await client.voices.list({ limit: 5 });
for (const voice of voices) {
console.log(` - ${voice.id}: ${voice.name}`);
}
// Generate audio with streaming
console.log('\nGenerating audio (streaming)...');
const chunks: ArrayBuffer[] = [];
let ttfa: number | undefined;
const startTime = Date.now();
await client.tts.stream(
{
text: 'Welcome to KugelAudio. This is an example of high-quality text-to-speech synthesis.',
modelId: 'kugel-3.5',
voiceId: 1071,
},
{
onChunk: (chunk) => {
if (!ttfa) {
ttfa = Date.now() - startTime;
console.log(`Time to first audio: ${ttfa}ms`);
}
chunks.push(base64ToArrayBuffer(chunk.audio));
},
onFinal: (stats) => {
console.log(`Generated ${stats.durationMs}ms of audio`);
console.log(`Generation time: ${stats.generationMs}ms`);
console.log(`RTF: ${stats.rtf}x`);
},
}
);
} finally {
// Release the pooled WebSocket and its keepalive timer
client.close();
}
}
main();import com.kugelaudio.sdk.*;
import java.nio.file.Path;
public class KugelAudioExample {
public static void main(String[] args) throws Exception {
try (KugelAudio client = new KugelAudio(
KugelAudioOptions.builder("your_api_key").build())) {
// List available models
System.out.println("Available Models:");
for (Model model : client.models().list()) {
System.out.printf(" - %s: %s%n", model.getId(), model.getName());
}
// List available voices
System.out.println("\nAvailable Voices:");
VoiceListResponse voiceResult = client.voices().list(null, null, 5, 0);
for (Voice voice : voiceResult.getVoices()) {
System.out.printf(" - %d: %s%n", voice.getId(), voice.getName());
}
// Generate audio
System.out.println("\nGenerating audio...");
AudioResponse audio = client.tts().generate(
GenerateRequest.builder(
"Welcome to KugelAudio. This is an example of high-quality text-to-speech synthesis."
)
.modelId("kugel-3.5")
.voiceId(1071)
.language("en")
.build()
);
System.out.printf("Generated %.2fs of audio in %.0fms (RTF: %.2f)%n",
audio.getDurationMs() / 1000.0, audio.getGenerationMs(), audio.getRtf());
// Save to file
audio.saveWav(Path.of("example.wav"));
System.out.println("Saved to example.wav");
}
}
}use kugelaudio::{KugelAudio, TtsRequest, VoiceListOptions};
#[tokio::main]
async fn main() -> kugelaudio::Result<()> {
// Initialize client
let client = KugelAudio::new(std::env::var("KUGELAUDIO_API_KEY").unwrap_or_default())?;
// List available models
println!("Available Models:");
for model in client.models().list().await? {
println!(" - {}: {}", model.id, model.name);
}
// List available voices
println!("\nAvailable Voices:");
let page = client.voices().list(&VoiceListOptions::default().limit(5)).await?;
for voice in &page.voices {
println!(" - {}: {}", voice.id, voice.name);
}
// Generate audio over the WebSocket, which also reports timing and usage
println!("\nGenerating audio...");
let request = TtsRequest::new(
"Welcome to KugelAudio. This is an example of high-quality text-to-speech synthesis.",
)
.model_id("kugel-3.5")
.voice_id(1071)
.language("en");
let audio = client.tts().stream(&request).await?.collect_response().await?;
println!(
"Generated {:.2}s of audio in {:.0}ms",
audio.duration_seconds(),
audio.generation_ms.unwrap_or_default()
);
println!("Real-time factor: {:.2}x", audio.rtf.unwrap_or_default());
// Save to file
audio.save_wav("example.wav")?;
println!("Saved to example.wav");
// Close client
client.close().await;
Ok(())
}#include <cstdlib>
#include <iostream>
#include <kugelaudio/kugelaudio.hpp>
int main() {
const char* key = std::getenv("KUGELAUDIO_API_KEY");
if (key == nullptr) return 1;
try {
// Initialize client
kugelaudio::Client client{key};
// List available models
std::cout << "Available Models:\n";
for (const auto& model : client.models().list()) {
std::cout << " - " << model.id << ": " << model.name << '\n';
}
// List available voices
std::cout << "\nAvailable Voices:\n";
kugelaudio::VoiceListOptions options;
options.limit = 5;
for (const auto& voice : client.voices().list(options).voices) {
std::cout << " - " << voice.id << ": " << voice.name << '\n';
}
// Generate audio over the WebSocket, which also reports timing and usage
std::cout << "\nGenerating audio...\n";
kugelaudio::GenerateRequest request;
request.text =
"Welcome to KugelAudio. This is an example of high-quality text-to-speech synthesis.";
request.model_id = "kugel-3.5";
request.voice_id = 1071;
request.language = "en";
kugelaudio::AudioResponse audio = client.tts().stream(request);
std::cout << "Generated " << audio.duration_seconds() << "s of audio in "
<< audio.generation_ms << "ms\n";
std::cout << "Real-time factor: " << audio.rtf << "x\n";
// Save to file
audio.save_wav("example.wav");
std::cout << "Saved to example.wav\n";
} catch (const kugelaudio::KugelAudioError& e) {
std::cerr << e.type_name() << ": " << e.what() << '\n';
return 1;
}
}import 'dart:io';
import 'package:kugelaudio/io.dart';
Future<void> main() async {
// Initialize client
final client = KugelAudio(apiKey: Platform.environment['KUGELAUDIO_API_KEY']!);
try {
// List available models
print('Available Models:');
for (final model in await client.models.list()) {
print(' - ${model.id}: ${model.name}');
}
// List available voices
print('\nAvailable Voices:');
final page = await client.voices.list(limit: 5);
for (final voice in page.voices) {
print(' - ${voice.id}: ${voice.name}');
}
// Generate audio over the WebSocket, which also reports timing and usage
print('\nGenerating audio...');
final audio = await client.tts
.stream(
text: 'Welcome to KugelAudio. This is an example of high-quality '
'text-to-speech synthesis.',
modelId: 'kugel-3.5',
voiceId: 1071,
language: 'en',
)
.collect();
print('Generated ${audio.durationMs.round()}ms of audio '
'in ${audio.generationMs?.round()}ms');
print('Real-time factor: ${audio.rtf}x');
// Save to file
await audio.saveWav('example.wav');
print('Saved to example.wav');
} finally {
// Close client
await client.close();
}
}Browser support
Not applicable: the Python SDK runs on your server.
The SDK also runs in modern browsers, using the native WebSocket. The
credential travels in the WebSocket URL, so never ship a project API key to a
browser: run the SDK on your backend and forward the audio to the browser.
Not applicable: the Java SDK runs on your server.
Not applicable: the Rust SDK runs on your server or device.
Not applicable: the C++ SDK runs on your server or device.
The package also runs in Flutter web, using the browser's WebSocket. Code in a
browser exposes its API key to anyone using the page, so never ship a project API key
there: call KugelAudio from your own server and stream the audio to the page.
Local turn detection (optional)
The Python SDK also ships an optional, local end-of-turn detector for voice agents. It runs on your machine, needs Python 3.11 or newer, and pulls in PyTorch, Transformers and ONNX Runtime:
pip install "kugelaudio[turn-detection]"
hf auth login # the model bundle is downloaded from Hugging FaceLoad the detector once per process and create one session per conversation.
Supported languages are de, en, es, it and nl. pcm_chunk stands for
16 kHz mono PCM16 bytes of the user's audio:
from kugelaudio.turn import TurnDetector, TurnMessage
detector = TurnDetector.from_pretrained()
turn = detector.create_session("en")
# Optional: prior dialogue, separate from the current user transcript
turn.update_history([
TurnMessage("user", "I'd like to book a trip."),
TurnMessage("assistant", "Where would you like to go?"),
])
# Feed the current user's audio and interim transcript continuously
turn.push_pcm16(pcm_chunk, sample_rate=16_000)
turn.update_transcript("To Berlin, and then")
# Call with the current silence duration whenever your VAD reports silence
decision = turn.observe_silence(duration_ms=200)
if decision.end_turn:
turn.reset_turn() # start the assistant response, then resetHistory requires a bundle trained for multiple context messages; single-message
bundles reject a nonempty history. Supply only messages already known at
scoring time and assistant text that was actually spoken. update_history()
replaces the history, reset_turn() keeps it, and reset_conversation()
clears it.
Not available in the TypeScript SDK; the local turn detector is Python only.
Not available in the Java SDK; the local turn detector is Python only.
Not available in the Rust SDK; the local turn detector is Python only.
Not available in the C++ SDK; the local turn detector is Python only.
Not available in the Dart SDK; the local turn detector is Python only.
Next steps
- Configuration: client options, authentication modes, regions, lifecycle
- Generate speech: one-shot generation, parameters, word timestamps, models
- Streaming: streaming sessions for LLM output, barge-in, multi-context sessions
- Text normalization: languages and spell tags
- Voices: list, create, and manage voices
- Dictionaries: per-project pronunciation and replacement lists
- Speech enhancement: remove background noise or keep one voice
- Types and errors: exceptions, data models, and enums
- Agent skill: teach your coding assistant to write correct KugelAudio code