Skip to content
KugelAudioDocs

Speech to Text in the SDKs

SDK reference for speech to text with kugel-ear-1: transcribe a recording with client.asr in Python, TypeScript and Java, and stream live audio with client.asr.stream in Python and TypeScript.

On this page

client.asr.transcribe sends a complete recording to kugel-ear-1 and returns its transcript, its language and spelling alternatives for uncertain words; client.asr.stream streams live audio and yields the transcript as it is heard. What the model does is in the Speech to text guide; the wire protocol, the limits and every error are in the API reference.

client.asr.transcribe is a blocking call; there is no async variant yet. client.asr.stream is an async iterator, and client.asr.stream_sync its blocking twin.

Transcribe a file

from kugelaudio import KugelAudio

client = KugelAudio(api_key="YOUR_API_KEY")

with open("call.wav", "rb") as f:
    result = client.asr.transcribe(f.read())

print(result.text)        # the transcript
print(result.language)    # language code, such as "de" or "fr"
print(result.duration_s)  # audio duration in seconds
for word in result.word_alternatives:
    print(word.word, [alt.spelling for alt in word.alternatives])

The recording can be WAV, FLAC, Ogg or most other common container formats, at 8 to 48 kHz, up to 300 seconds; see Audio limits. Without a language, the model detects German or English. For any other supported language, pass its code, such as language="fr" in Python, language: 'fr' in TypeScript, or "fr" as the language argument in Java. The SDKs send one code; to let the model detect the language among several, send a list to the REST endpoint directly.

Your vocabulary

Names, brands and domain terms you pass as boosted phrases come out in your spelling. The words of your project's dictionaries are added after them without sending anything; choose dictionaries per request, or none. How phrases work and their limits are in Custom vocabulary.

result = client.asr.transcribe(
    audio_bytes,
    filename="call.flac",
    content_type="audio/flac",
    boosted_phrases=["Kratzenstein", "KugelAudio", "Mayrhofer"],
    dictionary_ids=[12],  # only this dictionary; [] applies none
)

transcribe

Python
def transcribe(
    audio: bytes,
    *,
    filename: str = "audio.wav",
    content_type: str = "audio/wav",
    language: str | None = None,
    model: str | None = None,
    boosted_phrases: list[str] | None = None,
    project_id: int | None = None,
    dictionary_ids: list[int] | None = None,
) -> TranscriptionResponse
ArgumentTypeDescription
audiobytesThe complete recording file. Empty audio raises ValidationError before any request.
filename, content_typestrSent with the upload. Set them for a container other than WAV.
languagestr | NoneA two-letter code such as "fr" to transcribe in that language, or "auto". Omitted: German or English, detected. See Supported languages.
modelstr | NoneOmit it, or pass "kugel-ear-1".
boosted_phraseslist[str] | NoneYour vocabulary for this request.
project_idint | NoneThe project whose dictionaries apply. Omitted: the API key's project.
dictionary_idslist[int] | NoneOmitted: every active dictionary of the project. []: none.

Returns a TranscriptionResponse.

Stream live audio

client.asr.stream opens the WebSocket, sends your audio as mono 16-bit PCM while it reads, and yields every frame the server sends back: the partials, the final of each turn and its revision. Each example streams a file; a live source such as a microphone passes its chunks the same way as they arrive, as an iterable or async iterable with the sample rate. The Java SDK has no streaming client yet, so its example opens the socket itself and parses the frames with the SDK's types.

import asyncio

from kugelaudio import KugelAudio, load_audio_stream

client = KugelAudio(api_key="YOUR_API_KEY")

async def main():
    audio = load_audio_stream("call.wav")  # mono, 16-bit
    async for event in client.asr.stream(audio):  # ends after the last turn
        if event.revises_final:
            print("revised:", event.partial_text, event.language)
        elif event.is_final:
            print("final:", event.partial_text, event.turn_end_reason)
        elif event.type == "partial":
            print("partial:", event.partial_text)

asyncio.run(main())

The server ends each turn when the speaker finishes, as KugelTurn hears it, and the connection stays open for the next turn; after your last chunk the SDK sends end_of_speech, which ends the last one, and the iteration ends with it. A failure raises the same typed errors as every other call (see Errors). Pass language, turn_detection, turn_detection_level, boosted_phrases, dictionary_ids or project_id to stream as the client frames describe; on a stream, language can be one code or a list of codes.

async for event in client.asr.stream(
    microphone_chunks(),  # any iterable or async iterable of PCM16 bytes
    sample_rate=16000,
    language=["de", "fr"],
    boosted_phrases=["Kratzenstein", "KugelAudio"],
    dictionary_ids=[12],
):
    ...

A partial is a guess to replace, the final is the turn's result, and the revision that may follow it is a more accurate transcript of the same turn that also carries the code of the language it was transcribed in.

TranscriptionResponse

What transcribe returns. Field by field it is the upload response.

Python
class TranscriptionResponse:
    text: str                        # the transcript (OpenAI-compatible field)
    transcript: str                  # the same text
    language: str                    # language code, such as "de" or "fr"
    duration_s: float                # audio duration in seconds
    model: str                       # "kugel-ear-1"
    model_revision: str | None       # immutable revision of the model build
    word_alternatives: list[WordAlternatives]  # spellings for uncertain words
    raw: dict                        # the response body as received

class WordAlternatives:
    raw_word_index: int              # position of the word in transcript
    word: str                        # the word as written
    alternatives: list[SpellingAlternative]  # most likely first

class SpellingAlternative:
    spelling: str
    probability: float               # the alternatives of one word sum to 1

Streaming frames

One parsed frame of the stream: a partial, a final, or an alternatives frame after the final.

client.asr.stream yields one StreamingTranscriptionEvent per frame; StreamingTranscriptionEvent.from_dict(frame) parses a frame you read yourself:

Python
class StreamingTranscriptionEvent:
    partial_text: str                # complete rolling hypothesis; replace the previous one
    is_final: bool                   # True on the turn's final
    type: str                        # "partial" or "alternatives"
    turn_end_reason: str | None      # on the final: why the turn ended
    turn_end_confidence: float | None
    turn_end_inference_ms: float | None
    word_alternatives: list[WordAlternatives]
    model: str | None                # on the final and alternatives frames
    model_revision: str | None
    language: str | None             # on the turn's revision; None on partials
    revises_final: bool              # True on the turn's revision

Errors

transcribe and stream raise the same errors as every other SDK call, for example AuthenticationError for a rejected key, InsufficientCreditsError for a spent balance and RateLimitError for 429 (in Java AuthenticationException, InsufficientCreditsException and RateLimitException); see Types and errors. What each status means for speech to text is in the error table.

Next steps