Endpointing
Endpointing decides when an utterance ends: after a pause of endpointing seconds, its final transcript is
sent. It sets the trade-off between fast finals and whole sentences.
| field | range | when omitted |
|---|---|---|
endpointing |
0.01 to 10 s | 0.64 s |
maximum_duration_without_endpointing |
5 to 60 s | no limit |
{ "endpointing": 0.3, "maximum_duration_without_endpointing": 15 }In a session, with the SDKs:
# pip install gladiaio-sdkimport osimport sysimport threadingimport wave
from gladiaio_sdk import GladiaClient, LiveV2InitRequest
API_URL = os.environ.get("GLADIA_API_URL", "https://eu1.api.gladia.io")API_KEY = os.environ["GLADIA_API_KEY"]
with wave.open(sys.argv[1]) as wav: # a 16-bit PCM WAV file rate, channels = wav.getframerate(), wav.getnchannels() audio = wav.readframes(wav.getnframes())
config = { "encoding": "wav/pcm", "sample_rate": rate, "bit_depth": 16, "channels": channels, "model": "solaria-3", "language_config": { "languages": [ "en" ] }, "endpointing": 0.3, "maximum_duration_without_endpointing": 15}
client = GladiaClient(api_key=API_KEY, api_url=API_URL)session = client.live_v2().start_session(LiveV2InitRequest.from_dict(config))ended, errors = threading.Event(), []
@session.on("message")def on_message(message): if message.type == "transcript" and message.data.is_final: print(message.data.utterance.text)
session.on("error", errors.append)session.once("ended", lambda _: ended.set())
step = rate * channels * 2 // 10 # 100 ms of audio per chunkfor start in range(0, len(audio), step): session.send_audio(audio[start : start + step]) # live audio: send each chunk as it is capturedsession.stop_recording()
ended.wait(timeout=60)sys.exit(1 if errors or not ended.is_set() else 0)// npm install @gladiaio/sdkimport { readFileSync } from "node:fs";import { GladiaClient } from "@gladiaio/sdk";
const { sampleRate, channels, pcm } = readWav(process.argv[2]); // a 16-bit PCM WAV file
const config = { encoding: "wav/pcm", sample_rate: sampleRate, bit_depth: 16, channels, model: "solaria-3", language_config: { languages: [ "en" ] }, endpointing: 0.3, maximum_duration_without_endpointing: 15};
const client = new GladiaClient({ apiKey: process.env.GLADIA_API_KEY, apiUrl: process.env.GLADIA_API_URL ?? "https://eu1.api.gladia.io",});const session = client.liveV2().startSession(config);
session.on("message", (message) => { if (message.type === "transcript" && message.data.is_final) { const { text, end, forced } = message.data.utterance; console.log(`${end.toFixed(1)}s ${forced ? "(forced) " : ""}${text}`); }});session.on("error", (error) => { console.error(error); process.exitCode = 1;});session.once("ended", () => process.exit());
const step = (sampleRate * channels * 2) / 10; // 100 ms of audio per chunkfor (let start = 0; start < pcm.length; start += step) { session.sendAudio(pcm.subarray(start, start + step)); // live audio: send each chunk as it is captured}session.stopRecording();
function readWav(path) { const file = readFileSync(path); let fmt, pcm; for (let pos = 12; pos < file.length; ) { const id = file.toString("ascii", pos, pos + 4); const size = file.readUInt32LE(pos + 4); if (id === "fmt ") fmt = { channels: file.readUInt16LE(pos + 10), sampleRate: file.readUInt32LE(pos + 12) }; if (id === "data") pcm = file.subarray(pos + 8, pos + 8 + size); pos += 8 + size + (size % 2); } return { ...fmt, pcm };}Create the session, then stream its audio to the returned url (Live WebSocket):
curl -X POST https://eu1.api.gladia.io/v2/live \ -H "x-gladia-key: $GLADIA_API_KEY" \ -H "content-type: application/json" \ -d '{ "encoding": "wav/pcm", "sample_rate": 16000, "bit_depth": 16, "channels": 1, "model": "solaria-3", "language_config": { "languages": [ "en" ] }, "endpointing": 0.3, "maximum_duration_without_endpointing": 15 }'- A short
endpointing(0.2 to 0.4 s) gives faster finals, and cuts sentences at short hesitations. - A long one (0.8 s and more) gives whole sentences, later.
maximum_duration_without_endpointingforces a final after that much speech without a pause; such an utterance carries"forced": "max_duration".
GET /v2/live/defaults returns the values in force for your account.
For a voice agent, keep endpointing for the transcript and use end-of-turn detection to decide when to answer: a pause in the middle of a sentence is not the end of a turn.
Limits and gotchas
Section titled “Limits and gotchas”- Too short cuts words. Under 0.2 s, a breath or a hesitation ends the utterance: finals split mid-sentence.
maximum_duration_without_endpointingcuts anywhere. A forced final can end mid-word: keep it for monologues and noisy lines without pauses.- It is not end of turn. A pause ends an utterance, not the speaker’s turn: voice agents should answer on end of turn.
- Python SDK: an utterance’s
forcedis not in the SDK’s objects (gladiaio/sdk#61); the JavaScript SDK has it.
Number hold
Section titled “Number hold”A phone number, an amount or an address dictated in pieces (“my card number is 4970… 1234… 5678”) would be cut into several finals, one per pause. The number hold keeps the utterance open while the number is still being dictated, up to 2 seconds without new words, then closes it as usual.
It is on by default where the model supports it (Models); the session is never refused for it. Turn it off with:
{ "turn_router": false }