Dollyglot DollyglotDocs
Live demo
Speaker Gating API · v1.2

Voiceprint Speaker Gating

Given an enrolled speaker's voiceprint, the module analyses a 16 kHz audio stream in real time and reports, per frame, whether the enrolled speaker is talking — ignoring background noise, media audio and other voices.

Two calls: a REST endpoint to enroll a speaker, and a WebSocket to stream audio and receive gating events. Base URL https://gating.dollyglot.com. Interactive REST schema at /docs; live playground at the demo.

Try it live. Base URL https://gating.dollyglot.com · trial key dg-trial-7762c25375eb0479 (send as X-API-Key) · interactive demo.

Quickstart

  1. Enroll a speakerPOST 20–60 s of their speech to /v1/profiles and keep the returned profile_id.
  2. Open a streamConnect a WebSocket to /v1/stream?profile_id=….
  3. Send audio, read eventsSend 16 kHz PCM frames; receive one JSON event every 20 ms with the is_speaking flag and a similarity score.

Authentication

Every request needs an API key.

Each key carries a name and a daily streaming-minutes quota. Enrollment does not consume streaming quota.

Enroll a speaker

POST /v1/profiles

Creates a speaker profile from one or more audio files (20–60 s of speech recommended, ≥ 8 s minimum). Audio is processed in memory and discarded immediately — only the embedding is stored.

cURL
curl -X POST https://gating.dollyglot.com/v1/profiles \
  -H "X-API-Key: <key>" \
  -F "files=@speaker.wav"

Response

JSON
{ "profile_id": "9cda2a8f09954db28d91bee1f56f6de9" }

Delete a profile

DELETE /v1/profiles/{profile_id}

Returns {"deleted":"<id>"}, or 404 with profile_not_found.

Stream audio

WS /v1/stream?profile_id={id}

Open the socket, optionally send a config frame, then send binary PCM frames. The flag updates every 20 ms; the similarity score refreshes every 200 ms.

Client to server

MessageContent
config (optional, first frame){"sensitivity":"low|default|high"}
audiobinary — 16-bit LE PCM, 16 kHz mono, any chunk size (20–40 ms recommended)
end{"eof":true} — flush & close gracefully

Server to client

One JSON text frame every 20 ms:

JSON
{ "t_ms": 600, "is_speaking": true, "score": 0.83, "speech": true, "score_smoothed": 0.79 }
Onset latency is ~500 ms of target speech before the flag confirms — identifying who is speaking needs enough voice to be reliable. Configurable via sensitivity.
Network latency. That ~500 ms is processing latency measured at the module. Over this cloud WebSocket you also incur network round-trip — a few ms within Hong Kong, more from farther away or over congested links — which adds to the onset you observe in the demo. The on-device SDK removes the network hop entirely.

Event schema

FieldTypeDescription
t_msintaudio timestamp (ms)
is_speakingboolthe decision — is the enrolled speaker talking now (VAD + identity + hysteresis + timing)
scorefloat | nullraw voiceprint similarity in [0, 1], refreshed every 200 ms; null before enough speech
speechboolVAD — is any voice present (independent of identity)
score_smoothedfloat | nullEMA-smoothed score — the value the decision is made on

Error codes

On a problem the server sends {"error":"<code>","detail":"…"}. REST uses the HTTP status; WebSocket closes the socket.

CodeWhereMeaning
invalid_api_key401 / WS 1000missing or invalid key
unsupported_sample_rate415 / WSnot 16 kHz
stereo_not_supported415 / WSnot mono
unsupported_bit_depth415 / WSnot 16-bit PCM
insufficient_enrollment_audio422< 8 s of speech to enroll
profile_not_found404 / WS 1000unknown profile id
quota_exceededWS 1008daily streaming quota reached

Sensitivity

Presets map to the identity thresholds (turn-on / turn-off, with hysteresis). Tune on your own target speaker and acoustic scenario for production.

PresetOnOffBehaviour
low0.620.50strict — fewest false accepts
default0.550.45balanced (recommended)
high0.500.40most responsive

Example client

Python
import asyncio, json, soundfile as sf, websockets

async def main(host, key, pid, wav):
    audio, sr = sf.read(wav, dtype="int16")        # must be 16 kHz mono
    assert sr == 16000 and audio.ndim == 1
    raw = audio.tobytes()
    uri = f"{host}/v1/stream?profile_id={pid}&api_key={key}"
    async with websockets.connect(uri) as ws:
        await ws.send(json.dumps({"sensitivity": "default"}))
        async def send():
            for i in range(0, len(raw), 640):       # 20 ms frames
                await ws.send(raw[i:i+640]); await asyncio.sleep(0.02)
            await ws.send(json.dumps({"eof": True}))
        async def recv():
            async for m in ws:
                e = json.loads(m)
                if e.get("is_speaking"):
                    print(e["t_ms"], "SPEAKING", e["score"])
        await asyncio.gather(send(), recv())

asyncio.run(main("wss://gating.dollyglot.com", "<key>", "<profile_id>", "sample.wav"))
Privacy. Trial audio (enrollment and streamed) is processed transiently and never written to disk; only float32 embeddings are persisted, and profiles are deletable.
Dollyglot · Voiceprint Speaker Gating · API v1.2