Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Modellen Tools & Services
Oplossingen
Organisaties Ontwikkelaars Internet Service Providers Managed Service Providers AI-in-a-Box
Kenniscentrum
Ondersteuning Documentation Blog Downloads
Bedrijf
Over ons Onderzoek Vacatures Investeren Contact
Inloggen

Voice API

json
1
2
3
4
5
6
{
  "title": "Voice",
  "audience": "developer",
  "summary": "Voice catalog, TTS synthesis, STT transcription, voice sessions, and recording state.",
  "sort_order": 12
}

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:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
{
  "data": [
    {
      "voice_id": "vc_abc",
      "display_name": "Brand Voice",
      "language_primary": "en",
      "language_supported": ["en", "nl", "de"],
      "gender_hint": "female",
      "embedding_status": "ready",
      "scope": "tenant"
    }
  ],
  "meta": {
    "permission_denied": false,
    "count": 5,
    "allowlist_active": true,
    "default_voice_id": "vc_abc",
    "user_voice_id": "vc_xyz"
  }
}

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:

json
1
2
3
4
5
6
7
8
9
{
  "text": "Hello, how are you?",
  "voice_id": "vc_abc",
  "speed": 1.0,
  "response_format": "mp3",
  "instructions": "cheerful",
  "normalize_text": true,
  "language_hint": "en"
}

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:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "data": {
    "text": "Hello, how are you?",
    "language_detected": "en",
    "duration_ms": 2300,
    "segments": [
      {"text": "Hello,", "start_s": 0.0, "end_s": 0.8, "confidence": 0.95},
      {"text": "how are you?", "start_s": 0.8, "end_s": 2.3, "confidence": 0.92}
    ]
  }
}

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:

json
1
2
3
4
5
6
7
{
  "data": {
    "voice_id": "vc_abc",
    "voice_style": "warm and professional, calm pace",
    "voice_speed": 1.2
  }
}

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:

json
1
2
3
4
5
{
  "voice_id": "vc_abc",
  "voice_style": "warm and professional, calm pace",
  "voice_speed": 1.2
}
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:

json
1
2
3
4
{
  "allowed_voice_ids": ["vc_abc", "vc_def"],
  "default_voice_id": "vc_abc"
}

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:

json
1
{"on_the_record": false}

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)
Updated 2026-05-27 22:31:28 View source (.md) rev 1