Voice API
1 2 3 4 5 6 | |
Voice API#
Endpoints for ScaiWave's voice features: voice catalog, text-to-speech, speech-to-text, voice call sessions, and on/off-the-record state.
Voice catalog#
GET /v1/voices#
List voices the caller can see (filtered by the tenant allowlist when one is active).
Query params:
refresh(bool, default false) — bypass the 15-min server cache.
Response:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
When permission_denied is true, the user's role doesn't include
scaispeak:voice.read — render an "ask your admin" empty state.
TTS — Speak text#
POST /v1/tts/speak#
Synthesize text and return audio bytes inline.
Body:
1 2 3 4 5 6 7 8 9 | |
Only text is required. voice_id defaults through the resolution
chain (user pref → tenant default → first available).
Response: Raw audio bytes with Content-Type: audio/mpeg (or
whatever format was requested). Headers:
X-ScaiWave-Voice-Id— the voice actually used.X-ScaiWave-Audio-Duration-Ms— clip length.
Long text (>500 chars) goes through ScaiSpeak's async job path internally — the endpoint blocks until done (~30s max). The client gets audio back either way.
Errors:
| Code | Status | Meaning |
|---|---|---|
SW_VOICE_PERMISSION_DENIED |
403 | Missing scaispeak:synthesize |
SW_VOICE_NO_VOICE_AVAILABLE |
404 | No usable voice in catalog |
SW_VOICE_BACKEND_UNAVAILABLE |
503 | TTS engine down |
SW_VOICE_EMPTY_TEXT |
400 | Text was empty |
STT — Transcribe audio#
POST /v1/stt/transcribe#
Transcribe a short audio clip.
Body: multipart/form-data
file— WAV audio (16 kHz mono PCM required)language_hint(optional) — ISO 639-1
Response:
1 2 3 4 5 6 7 8 9 10 11 | |
Max upload: 8 MiB. Clips longer than ~5 min go through ScaiEcho's async job path (transparent to the caller).
User voice preference#
GET /v1/users/me/voice-preference#
Read the user's voice preferences.
Response:
1 2 3 4 5 6 7 | |
All fields may be null if the user has not set a preference.
PUT /v1/users/me/voice-preference#
Update the user's voice preferences. All fields are optional — only the fields present are written (partial update).
Body:
1 2 3 4 5 | |
| Field | Type | Description |
|---|---|---|
voice_id |
string | null | Default voice. null to clear (falls back to tenant default). |
voice_style |
string | null | Free-text TTS style instructions passed to ScaiVoice session create (e.g. "warm and professional, calm pace"). null to clear (uses ScaiVoice default). |
voice_speed |
float | null | TTS playback speed, clamped to 0.5–2.0. null to clear (uses 1.0). |
Preferences are stored in the user's notification_prefs JSON
(no migration needed).
Tenant voice config (admin)#
GET /v1/admin/voice-config#
Read the tenant's voice allowlist + default. Returns the full unfiltered catalog for admin selection.
PUT /v1/admin/voice-config#
Update allowlist and/or default. Partial — only the fields present are written.
Body:
1 2 3 4 | |
Set allowed_voice_ids to null to clear the allowlist (all
voices visible). Set to [] to hide all voices from non-admins.
Voice recording state#
GET /v1/rooms/{room_id}/voice-recording#
Current on/off-the-record state for the room's voice call.
Defaults to true (on-record).
PUT /v1/rooms/{room_id}/voice-recording#
Flip the on/off-the-record flag. Requires room membership.
Body:
1 | |
Broadcasts swp.room.voice_recording on the room's WS channel so
all connected clients update live.
Voice sessions (internal)#
POST /v1/voice-sessions#
Create a voice session (called by the voice bot worker, not directly by browser clients). See Voice mode architecture for the full flow.
POST /v1/voice-agent/turn#
ScaiVoice cognition callback — server-to-server, authenticated via HMAC session token. Not callable by browser clients.
WebSocket events#
| Event | When |
|---|---|
swp.room.voice_recording |
On/off-record flag changed (user toggle or LLM marker) |
swp.room.message (msgtype swp.voice_transcript) |
User's speech transcribed and persisted (on-record) |
swp.room.message (msgtype swp.voice_reply) |
AI's voice reply persisted (on-record) |