Live API (Realtime)
The Live API streams audio over a WebSocket connection and returns incremental transcription (and, optionally, translation) results as the audio arrives — no need to wait for the full recording before you get text back.
Migrating from OpenAI's Realtime API?
This API deliberately mirrors the shape of OpenAI's Realtime API (same connection path, same two-step auth flow, same client/server event names) so that existing Realtime integrations can switch to Vulavula largely by changing an endpoint. See Differences from OpenAI's Realtime API below for the parts that don't carry over.
Connecting
Connecting has two steps: mint a short-lived client secret from your backend, then open the WebSocket from your client using that secret.
Step 1 — Mint a client secret
POST https://api.lelapa.ai/v1/realtime/client_secrets
This call must be made server-side — it requires your real X-CLIENT-TOKEN and should
never be embedded in browser or mobile client code. Your backend calls this endpoint, then
hands the returned short-lived value to the client that will actually open the WebSocket.
Headers
| Header | Type | Required | Description |
|---|---|---|---|
X-CLIENT-TOKEN | string | Yes | API token generated for the project |
Content-Type | string | Yes | Must be set to application/json |
Body
| Field | Type | Required | Description |
|---|---|---|---|
expires_after.anchor | string | No | Currently only "created_at" (default) |
expires_after.seconds | integer | No | Client secret lifetime in seconds (default 600, min 10, max 7200) |
session | object | No | Initial session config — see Session Configuration. Can also be sent later via session.update. |
🟢 200 OK
Code
🟠 401 Unauthorized
Step 2 — Open the WebSocket
WSS wss://api.lelapa.ai/v1/realtime
Browsers cannot set arbitrary headers during a WebSocket handshake, so authentication is
passed via the Sec-WebSocket-Protocol field instead — the same workaround OpenAI's Realtime
API uses. Pass an array of subprotocols to your WebSocket client:
Code
The vulavula-insecure-api-key. prefix is named that way deliberately (mirroring OpenAI's
own openai-insecure-api-key. naming) — it's a reminder that this value is a bearer
credential that will be visible to anything that can see the WebSocket handshake (e.g.
browser devtools), which is exactly why it should be short-lived and minted server-side
rather than a copy of your real X-CLIENT-TOKEN.
If you're connecting from a trusted backend process (not a browser), you can skip the
client-secret step entirely and pass your X-CLIENT-TOKEN directly the same way — but the
two-step flow is required for any client-side code.
Session Configuration
Send a session.update event right after connecting (or include session in the
client-secret request) to configure the session:
Code
| Field | Type | Required | Description |
|---|---|---|---|
audio.input.format.type | string | No | Input audio encoding: "audio/pcm" (default), "audio/pcmu" (G.711 μ-law), or "audio/pcma" (G.711 A-law) — the same set OpenAI's Realtime API accepts. |
audio.input.format.rate | integer | No | Input sample rate in Hz for "audio/pcm" (default 24000; 16000 is also supported). Ignored for "audio/pcmu"/"audio/pcma", which are always 8kHz. |
audio.input.noise_reduction | object | null | No | Accepted for compatibility but not implemented — any value is logged and ignored. |
audio.input.transcription.language | string | No | Source language — see Supported Language Codes below. Defaults to zu (isiZulu) if omitted. |
audio.input.transcription.model | string | No | Accepted for compatibility but not implemented — this API runs one fixed ASR model; any value is logged and ignored. |
audio.output.language | string | null | No | Target language to translate into — see Supported Language Codes below. Omit or set to null for transcription-only — this is the single switch between the two modes. |
Supported Language Codes
Language codes are ISO 639-1 (two-letter) or ISO 639-3 (three-letter), matching OpenAI's Speech-to-Text convention — either form works.
| Language | ISO 639-1 | ISO 639-3 | Transcribe | Translate target |
|---|---|---|---|---|
| isiZulu | zu | zul | ✅ | ✅ |
| Sesotho | st | sot | ✅ | ✅ |
| Afrikaans | af | afr | ✅ | ✅ |
| South African English | en | eng | ✅ | ✅ |
| African French | fr | fra | ✅ | — |
| Northern Sotho | — | nso | — | ✅ |
| Swati | ss | ssw | — | ✅ |
| Tsonga | ts | tso | — | ✅ |
| Tswana | tn | tsn | — | ✅ |
| Xhosa | xh | xho | — | ✅ |
| Swahili | sw | swh | — | ✅ |
Code-switched isiZulu (alpha) is available for transcription via the Vulavula-specific
extension code cs-zul — this one isn't part of ISO 639 since it's not a single language.
Client Events
| Event | Description |
|---|---|
session.update | Set the session configuration (see above). Only the first session.update (or the config given at client-secret creation) takes effect — config is fixed once audio streaming starts, and a later session.update gets an error instead. |
session.input_audio_buffer.append | Append audio to the input buffer. audio is base64-encoded mono audio in whatever audio.input.format was configured. |
session.close | Gracefully close the session after flushing any pending audio. |
Code
Server Events
| Event | Description |
|---|---|
session.created | Sent automatically as the first event after the WebSocket opens. |
session.updated | Confirms a session.update was applied. Same shape as session.created. Only sent for the first session.update on a session — config is fixed once audio streaming starts, so later session.updates get an error (session_already_active) instead. |
session.input_transcript.delta | The finalized source-language text for one completed segment of speech. delta is safe to append to a running transcript — segments are sent once each, only after the model has settled on that segment's text (interim/revisable hypotheses are not sent). |
session.output_transcript.delta | The finalized translated text for one completed segment. Only sent when audio.output.language is configured. Same append-safety guarantee as above. |
session.closed | Sent after a session.close (or server-initiated close) once the session has fully ended. |
error | A recoverable or non-recoverable error. See error.message/error.code for details. |
Example: session.input_transcript.delta
Code
Example: session.output_transcript.delta
Code
Example: error
Code
Differences from OpenAI's Realtime API
This API is intentionally close to OpenAI's Realtime API, but it is not a byte-for-byte clone. Known gaps:
- No WebRTC transport. WebSocket only — OpenAI recommends WebRTC for browser clients; we don't currently offer that transport.
- No translated-audio output. Vulavula's translation is text-only. OpenAI's translation
mode can also stream synthesized translated speech (
session.output_audio.delta); this API never emits that event. - One unified session type, not separate transcription-only and translation resources.
Toggle translation on/off per session with
audio.output.language, rather than connecting to a different endpoint.session.typeis always reported as"translation", matching OpenAI's translation session discriminator, regardless of whether translation is enabled for that session. - Different subprotocol prefix.
vulavula-insecure-api-key.instead ofopenai-insecure-api-key.— same mechanism, different namespace.
Full Example
See the runnable Python and browser examples in
vulavula-examples for a complete
end-to-end client.

