Realtime API
Client secrets
A browser should never hold your API key. Create a client secret on your server: it carries the session’s configuration and opens one session.
Create a client secret
- POST
https://api.dotwave.ai/v1/realtime/client_secretsAuthenticate with your API key. This is the path the OpenAI SDK calls.
curl https://api.dotwave.ai/v1/realtime/client_secrets \
-H "Authorization: Bearer $DOTWAVE_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"expires_after": {"anchor": "created_at", "seconds": 60},
"session": {
"type": "transcription",
"audio": {
"input": {
"format": {"type": "audio/pcm", "rate": 24000},
"transcription": {
"model": "nemotron-asr-streaming",
"language": "pt-BR"
}
}
}
}
}'expires_after.seconds- How long the secret stays valid: 60 by default, 600 at most.
session.typetranscription.session.audio.input- The format, the model and language under
transcription, andturn_detection, as insession.update. See client events. turn_detection.idle_timeout_ms- How long the session stays open without audio: 30000 by default, from 10000 to 300000.
The response
Fields without effect come back as null. session.live_url is the socket to open.
{"value": "…", "expires_at": 1790000000,
"session": {"id": "sess_…", "object": "realtime.transcription_session",
"type": "transcription", "model": "nemotron-asr-streaming",
"audio": {"input": {"format": {"type": "audio/pcm", "rate": 24000},
"transcription": {"model": "nemotron-asr-streaming",
"language": "pt-BR", "prompt": null},
"noise_reduction": null,
"turn_detection": {"type": "server_vad", "threshold": null,
"prefix_padding_ms": null,
"silence_duration_ms": 3200,
"idle_timeout_ms": 30000}}},
"live_url": "wss://api.dotwave.ai/v1/realtime"}}Connect from a browser
Browsers cannot set headers on a WebSocket, so pass the secret as the token query parameter:
// `secret` is the `value` your server received. The session is
// already configured, so the browser only sends audio.
const ws = new WebSocket(
`wss://api.dotwave.ai/v1/realtime?token=${encodeURIComponent(secret)}`,
);
ws.onmessage = ({ data }) => {
const event = JSON.parse(data);
if (event.type === 'conversation.item.input_audio_transcription.delta') {
captions.textContent += event.delta;
}
};
// 24 kHz mono PCM16 from an AudioWorklet, base64-encoded.
function sendAudio(base64: string) {
ws.send(JSON.stringify({ type: 'input_audio_buffer.append', audio: base64 }));
}A secret opens one session and expires at expires_at. An unused secret counts toward your concurrency limit until it expires. A secret from this endpoint can open the Deepgram-compatible socket instead.