Diarization
Attribute each word to a speaker in multi-speaker audio, and read a speaker field that is either an index or absent.
Overview
Diarization attributes each transcribed word to a speaker. Turn it on and every word the diarizer can place carries a speaker label. Use it for interviews, meetings, and two-party calls.
Request
curl https://api.sprag.ai/v1/audio/transcriptions \
-H "Authorization: Bearer $SPRAG_API_KEY" \
-F [email protected] \
-F model=rhythm \
-F response_format=verbose_json \
-F speaker_labels=true \
-F num_speakers=2| Field | Type | Default | Notes |
|---|---|---|---|
response_format | string | json | Must be verbose_json. |
speaker_labels | boolean | false | Turns diarization on. |
num_speakers | integer | unset | Sets the number of speakers for the diarizer. |
Send response_format=verbose_json. The speaker label is a field on each word
object, and word objects only exist on the verbose path, so speaker_labels
with any other format returns 400. You do not need timestamp_granularities[]
as well; if you do send one, keep word in it, since a list that omits word
drops the words array and the request returns 400. See
timestamps for that payload.
Pass num_speakers when you know the count. It sets the count the diarizer
works to rather than hinting at it, and a diarizer left to infer can turn a
noisy segment into an extra speaker. Omit it to let it infer. Without
speaker_labels it is ignored.
Response
Each attributed word gains a speaker, a 0-indexed integer.
{
"text": "So how did the migration go? Better than expected.",
"duration": 3.4,
"words": [
{ "word": "So", "start": 0.10, "end": 0.22, "speaker": 0 },
{ "word": "how", "start": 0.22, "end": 0.36, "speaker": 0 },
{ "word": "Better", "start": 1.51, "end": 1.88, "speaker": 1 },
{ "word": "than", "start": 1.88, "end": 2.02, "speaker": 1 }
],
"usage": { "input": { "audio": { "duration": "3" } } }
}Speaker 0 is whoever the diarizer heard first, and the numbering is per
request, so the same person in two separate files will not reliably get the same
index. Do not carry an index across requests as a speaker identity.
Reading the speaker field
speaker has two states on the wire, an integer or no key at all. The response
omits null fields, so a word the diarizer could not place arrives with the key
missing, which is indistinguishable from a word in a transcript where
diarization never ran.
| State | Meaning |
|---|---|
| An integer | The diarizer attributed this word to that speaker. |
| Key absent | No attribution: either diarization did not run, or it ran and left this word unplaced. |
Branch on your own request rather than on the word, since only the request tells you whether diarization ran. Expect unplaced words inside an otherwise diarized transcript where speech overlaps or segments are very short.
Test for the key, not the value. Speaker 0 is a valid label and reads as
falsy, so a check like if (word.speaker) drops it.
if (!("speaker" in word)) {
// unattributed: diarization did not run, or could not place this word
} else {
// word.speaker is the 0-indexed label
}Grouping words into turns
The API returns per-word labels, not turns, so collapse runs of the same speaker client-side. Decide there how to handle unattributed words: joining them to the surrounding run usually reads better than emitting a one-word turn with no speaker.
Realtime
Diarization is a transcriptions-endpoint feature. A realtime session delivers transcripts as input-transcription events, and those carry no speaker labels.