Skip to main content

Speech to Text Streaming API

The Streaming API receives audio chunks over WebSocket and returns transcript deltas and completion events. Event names follow an OpenAI Realtime transcription-compatible shape, while the connection path is specific to Kitsch Labs.

Connection

wss://api.kitschlabs.com/v1/speech-to-text/stream

Include one of the following authentication headers in the WebSocket handshake from your server environment.

Authorization: Bearer <API_KEY>

You can also use xi-api-key. The standard browser WebSocket API cannot set arbitrary authentication headers. Keep the API key on your server and connect from there.

After authentication, the server sends session.created first.

Session configuration

Send one session.update before sending audio.

{
"type": "session.update",
"session": {
"type": "transcription",
"audio": {
"input": {
"format": {"type": "audio/pcm", "rate": 24000},
"transcription": {
"model": "kitsch-stt-v1",
"language": "en",
"prompt": "Kitsch Labs"
},
"turn_detection": {"type": "server_vad"}
}
}
}
}

The server returns the applied settings in session.updated. The session cannot be changed after the first audio chunk.

Supported audio formats

FormatInput requirements
audio/pcm24 kHz, mono, signed 16-bit little-endian PCM
audio/pcmu8 kHz, mono, G.711 μ-law
audio/pcma8 kHz, mono, G.711 A-law

The default is audio/pcm at 24 kHz.

How do I send audio?

Base64-encode each chunk. One decoded chunk can be up to 256 KiB.

{"type":"input_audio_buffer.append","audio":"BASE64_AUDIO"}

The default server_vad automatically detects the end of an utterance. For manual turns, set turn_detection to null, then send:

{"type":"input_audio_buffer.commit"}

What events will I receive?

EventDescription
session.createdTranscription session created
session.updatedSession settings applied
input_audio_buffer.committedOne utterance has been committed
conversation.item.input_audio_transcription.deltaFinalized transcript fragment
conversation.item.input_audio_transcription.completedComplete transcript and usage for the utterance
errorRequest or processing error

Delta event:

{
"type": "conversation.item.input_audio_transcription.delta",
"event_id": "event_...",
"item_id": "item_...",
"content_index": 0,
"delta": "Hello"
}

Completion event:

{
"type": "conversation.item.input_audio_transcription.completed",
"event_id": "event_...",
"item_id": "item_...",
"content_index": 0,
"transcript": "Hello.",
"usage": {"type": "duration", "seconds": 1.25}
}

Append deltas in order by item_id and content_index. Use the completed event's transcript as the final result for that utterance.

Connection example

This example sends a 24 kHz mono signed 16-bit little-endian raw PCM file.

import asyncio
import base64
import json
import os

from websockets.asyncio.client import connect


async def transcribe() -> None:
headers = {"Authorization": f"Bearer {os.environ['KITSCH_API_KEY']}"}
async with connect(
"wss://api.kitschlabs.com/v1/speech-to-text/stream",
additional_headers=headers,
) as websocket:
print(await websocket.recv()) # session.created

await websocket.send(json.dumps({
"type": "session.update",
"session": {
"type": "transcription",
"audio": {
"input": {
"format": {"type": "audio/pcm", "rate": 24000},
"transcription": {"model": "kitsch-stt-v1", "language": "en"},
"turn_detection": None,
}
},
},
}))

with open("speech.pcm", "rb") as audio:
while chunk := audio.read(64 * 1024):
await websocket.send(json.dumps({
"type": "input_audio_buffer.append",
"audio": base64.b64encode(chunk).decode("ascii"),
}))

await websocket.send(json.dumps({"type": "input_audio_buffer.commit"}))

async for raw_event in websocket:
event = json.loads(raw_event)
if event["type"] == "conversation.item.input_audio_transcription.delta":
print(event["delta"], end="", flush=True)
elif event["type"] == "conversation.item.input_audio_transcription.completed":
print()
break
elif event["type"] == "error":
raise RuntimeError(event["error"]["message"])


asyncio.run(transcribe())

Limits and billing

  • Each connection has a maximum session length of 30 seconds.
  • Connection time is rounded up to the next started second and billed at 0.1 credits per second.
  • One connection can process multiple utterances, but open a new session before the 30-second limit.
  • After a temporary connection error, reconnect with a new session. Do not automatically replay audio from the previous session.

Error handling

Unsupported events and invalid settings can return an error event while the connection remains open.

{
"type": "error",
"event_id": "event_...",
"error": {
"type": "invalid_request_error",
"code": "invalid_audio",
"message": "The audio field must be valid base64.",
"param": null,
"event_id": "<REQUEST_ID>"
}
}
Close codeMeaningRecommended action
1000Normal completion or session limitOpen a new session if needed
1008Authentication, permission, or request policy errorCheck the key, credits, and settings
1011Temporary processing errorReconnect selectively
1013Concurrent processing capacity reachedApply backoff before reconnecting