Skip to content

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
FieldTypeDefaultNotes
response_formatstringjsonMust be verbose_json.
speaker_labelsbooleanfalseTurns diarization on.
num_speakersintegerunsetSets 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.

StateMeaning
An integerThe diarizer attributed this word to that speaker.
Key absentNo 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.