> ## Documentation Index
> Fetch the complete documentation index at: https://aircaps.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Speaker identification

> Recognize enrolled people by voice with AirCaps Speaker ID-v1: enroll, then score clips.

AirCaps Speaker ID-v1 tells you **which of your enrolled people a clip sounds like**. You enroll each person with one or more voice clips, then send a clip and get back a similarity score for every enrolled speaker, best first. Use it to put names on diarized speakers, check that the expected person is speaking, or route audio by speaker.

It is synchronous: one clip per request, and the scores are the response. There are no jobs to poll and no webhooks.

> [!TIP]
>
> **Want names in a transcript?** Add `speaker_identification` when you [create a transcript](https://aircaps.ai/docs/guides/async-transcription.md#speaker-names), or to the `start` message of a [live stream](https://aircaps.ai/docs/guides/streaming.md#speaker-names) with speaker labels: each speaker gets the name of the enrolled speaker they match when the score reaches your threshold, with no cropping or clips to send.

> [!NOTE]
>
> AirCaps Speaker ID-v1 is enabled per account. If `GET /v1/account` shows `"enabled": false` for `aircaps-speaker-id-v1`, requests return `403 model_access_denied`. To get access, [talk to sales](https://aircaps.ai/contact).

## How it works

1. A **speaker** is a name plus optional metadata that you create.
2. Each **enrollment** is one clip of that speaker. The model turns it into a voice embedding, which is stored; the audio is not. A speaker's **profile** combines all of its enrollments.
3. **Identify** compares a clip with every profile and returns a similarity `score` for each speaker, from -1 to 1. Higher means more alike.
4. **You decide.** There is no threshold and no "unknown" verdict: your application chooses what score to accept. [Scores and thresholds](#scores-and-thresholds) gives measured starting points.

> [!WARNING]
>
> **The whole clip is compared, exactly as you send it.** Silence, music, background voices and crosstalk all become part of the voice the model hears. Before you send a clip, crop it to **one person speaking**: for example, use the segments from [AirCaps Diarization-v1](https://aircaps.ai/docs/guides/diarization.md) or the utterance times of a [transcript](https://aircaps.ai/docs/guides/async-transcription.md). Cropping, choosing which clips to send and combining results over time are up to your application. (For recordings you transcribe with A5S Async v1, or live streams with A5S Small V2 Streaming, `speaker_identification` does all of this for you: see [transcripts](https://aircaps.ai/docs/guides/async-transcription.md#speaker-names) and [live streams](https://aircaps.ai/docs/guides/streaming.md#speaker-names).)

## 1. Create a speaker

```bash theme={null}
curl https://api.aircaps.com/v1/speakers \
  -H "Authorization: Bearer $AIRCAPS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Ana García", "metadata": {"employee_id": "E-1042"}}'
```

The response is a [speaker](https://aircaps.ai/docs/api-reference/objects/enrolled-speaker.md) with an `id` (`spk_…`) and no enrollments yet. `name` and `metadata` (any JSON object up to 16 KB) are optional; use pseudonymous labels if you prefer.

## 2. Enroll clips

Send each clip's **raw audio bytes as the request body** (not multipart form data). Any common audio or video format works; channels are mixed down and audio is resampled to 16 kHz.

```bash theme={null}
curl https://api.aircaps.com/v1/speakers/spk_01k73a9x8w7v6u5t4s3r2q1p0n/enrollments \
  -H "Authorization: Bearer $AIRCAPS_API_KEY" \
  -H "Content-Type: audio/wav" \
  --data-binary @ana_intro.wav
```

```json theme={null}
{
  "id": "enr_01k73b5f1h2j3k4m5n6p7q8r9s",
  "object": "speaker_enrollment",
  "speaker_id": "spk_01k73a9x8w7v6u5t4s3r2q1p0n",
  "audio_duration": 15.42,
  "consistency": 0.712,
  "created_at": "2026-10-08T18:04:52.015Z",
  "speaker": { "id": "spk_01k73a9x8w7v6u5t4s3r2q1p0n", "num_enrollments": 2, "enrolled_duration": 27.9, "...": "..." }
}
```

`consistency` shows how well this enrollment matches the speaker's other enrollments (`null` for the first one). If one enrollment scores much lower than the others, it may be someone else's voice or a poor recording: [delete it](https://aircaps.ai/docs/api-reference/delete-enrollment.md).

### Enrollment audio: what works best

| Recommendation | Why |
| - | - |
| **20–30 seconds of the person's speech in total**, for example 2–3 clips of about 10 s each | More enrollment speech raises the weakest same-person scores. Gains level off after about 20–30 s |
| **Several clips rather than one**, ideally from different recordings | For the same total, several clips do at least as well as one long clip, and clips from different sessions cover more of how the person sounds |
| **One person speaking**, with no music and little background noise | Everything in the clip is treated as the person's voice |
| **Natural speech** (a conversation, reading a paragraph), not single words | Each clip must be at least 1 s; clips of several seconds represent a voice much better than very short ones |
| **The same kind of microphone and room you will identify from** | A profile enrolled on a headset and compared with a far-away room microphone typically scores lower |
| **Lossless or high-bitrate audio** (WAV, FLAC, Opus or AAC at normal bitrates) | Very low-bitrate audio (such as 24 kbps MP3) lowers scores noticeably |

A single clean 5–10 s clip is enough to get started. A speaker can have up to 20 enrollments.

## 3. Identify a clip

```bash theme={null}
curl https://api.aircaps.com/v1/speakers/identify \
  -H "Authorization: Bearer $AIRCAPS_API_KEY" \
  -H "Content-Type: audio/wav" \
  --data-binary @clip.wav
```

```json theme={null}
{
  "id": "sid_01k73c0q9w8e7r6t5y4u3i2o1p",
  "object": "speaker_identification",
  "model": "aircaps-speaker-id-v1",
  "created_at": "2026-10-08T18:06:30.551Z",
  "audio_duration": 4.21,
  "speakers_compared": 3,
  "margin": 0.476,
  "results": [
    {"speaker_id": "spk_01k73a9x8w7v6u5t4s3r2q1p0n", "name": "Ana García", "score": 0.684,
     "enrollment_scores": [{"enrollment_id": "enr_01k73b2c4d5e6f7g8h9j0k1m2n", "score": 0.661},
                           {"enrollment_id": "enr_01k73b5f1h2j3k4m5n6p7q8r9s", "score": 0.648}]},
    {"speaker_id": "spk_01k73a7m6n5b4v3c2x1z0l9k8j", "name": "Ben Ito", "score": 0.208,
     "enrollment_scores": [{"enrollment_id": "enr_01k73a8h7g6f5d4s3a2p1o0i9u", "score": 0.208}]},
    {"speaker_id": "spk_01k73a5q4w3e2r1t0y9u8i7o6p", "name": null, "score": 0.031,
     "enrollment_scores": [{"enrollment_id": "enr_01k73a6z5x4c3v2b1n0m9l8k7j", "score": 0.031}]}
  ]
}
```

* `results` lists every compared speaker, best first. `score` compares the clip with the speaker's profile (all of their enrollments together); `enrollment_scores` compares it with each enrollment on its own.
* `margin` is the best score minus the second best (`null` when only one speaker was compared).
* By default the clip is compared with **every enrolled speaker** in your account. Limit it with `speaker_ids` (comma-separated, or the parameter repeated), for example the people invited to a meeting. Return fewer results with `top_k`.
* **To verify one person**, pass just their ID: `?speaker_ids=spk_01k73a9x8w7v6u5t4s3r2q1p0n`, and compare the `score` with your threshold.
* Clips are 0.5 s to 2 minutes long. Identification results are not stored.

## Scores and thresholds

A score is a similarity, not a probability. What counts as "the same person" depends on your audio, so treat the numbers below as a starting point and check them on your own recordings.

We measured them on meeting recordings from a single far-field microphone. Each person was enrolled from one meeting with about 15 s of speech and identified in other meetings among the people present. Clips were cropped to one person's speech.

| Clip length | Correct top match | Same person: typical score (90% above) | Different person: typical score (90% below) |
| -: | -: | -: | -: |
| 1 s | 95% | 0.58 (0.34) | 0.11 (0.28) |
| 2 s | 97.5% | 0.68 (0.50) | 0.11 (0.29) |
| 3 s | 99% | 0.73 (0.58) | 0.11 (0.29) |
| 5 s | 99.7% | 0.77 (0.64) | 0.10 (0.30) |
| 10 s | 100% | 0.82 (0.68) | 0.11 (0.29) |

**Longer clips are the biggest lever.** Clips of 3 s or more are much more reliable than 1 s clips; if your segments are short, combine several from the same speaker before identifying.

How common decision rules behaved in the same test:

| Accept the top speaker when… | 2 s clips: right / wrong / no answer | 5 s clips: right / wrong / no answer | Person not enrolled, wrongly named (2 s / 5 s) |
| - | - | - | - |
| `score ≥ 0.4` | 95% / 1.1% / 3% | 99% / 0.3% / 0% | 6.1% / 5.0% |
| `score ≥ 0.5` | 91% / 0.5% / 9% | 99% / 0.3% / 1% | 1.1% / 2.2% |
| `score ≥ 0.5` and `margin ≥ 0.2` | 86% / 0.2% / 14% | 96% / 0% / 4% | 0.9% / 1.7% |
| `score ≥ 0.6` and `margin ≥ 0.2` | 75% / 0.2% / 25% | 93% / 0% / 7% | 0.2% / 0% |

* **A good default:** accept the top speaker when `score ≥ 0.5` and `margin ≥ 0.2`; otherwise treat the clip as unknown.
* **If everyone who might speak is enrolled** (a closed group), a lower score such as 0.4 names more clips with few mistakes.
* **If unenrolled people may speak**, or a wrong name is costly, require a higher score (0.6) and a margin. The last column is the rate at which someone who isn't enrolled gets named as somebody else.
* Enrolling more speech per person (20–30 s) and comparing only against the people who could be present (`speaker_ids`) both reduce mistakes.

## Python example

```python theme={null}
import os, requests

API = "https://api.aircaps.com/v1"
H = {"Authorization": f"Bearer {os.environ['AIRCAPS_API_KEY']}"}

def post_clip(path, wav_path, **params):
    with open(wav_path, "rb") as f:
        r = requests.post(f"{API}{path}", headers={**H, "Content-Type": "audio/wav"}, data=f, params=params)
    r.raise_for_status()
    return r.json()

# enroll
ana = requests.post(f"{API}/speakers", headers=H, json={"name": "Ana García"}).json()
for clip in ("ana_1.wav", "ana_2.wav", "ana_3.wav"):
    post_clip(f"/speakers/{ana['id']}/enrollments", clip)

# identify, then apply your own rule
res = post_clip("/speakers/identify", "segment_0042.wav")
top = res["results"][0]
if top["score"] >= 0.5 and (res["margin"] is None or res["margin"] >= 0.2):
    print("speaker:", top["name"], top["score"])
else:
    print("unknown speaker; best guess", top["name"], top["score"])
```

To cut a clip out of a longer recording first, for example the segment from 12.3 s to 19.8 s:

```bash theme={null}
ffmpeg -ss 12.3 -to 19.8 -i meeting.wav -ac 1 -ar 16000 segment_0042.wav
```

## Managing speakers

* [List](https://aircaps.ai/docs/api-reference/list-speakers.md) and [get](https://aircaps.ai/docs/api-reference/get-speaker.md) speakers with their enrollments; [rename or change metadata](https://aircaps.ai/docs/api-reference/update-speaker.md).
* [Delete an enrollment](https://aircaps.ai/docs/api-reference/delete-enrollment.md) to drop a bad clip; the profile is recomputed from the rest.
* [Delete a speaker](https://aircaps.ai/docs/api-reference/delete-speaker.md) to remove the person and all their voice data, immediately and permanently.
* Speakers are kept until you delete them; there is no automatic expiry. You can always read and delete them, even if your account loses access to the model.

## Limits and usage

* Each clip that is processed (enrollment or identification) counts its duration toward your [usage limit](https://aircaps.ai/docs/limits.md), shared by all models. Rejected clips don't count.
* Clips: 0.5 s to 2 minutes (enrollments at least 1 s), up to 100 MB, one per request.
* 1,000 speakers per account, 20 enrollments per speaker, 600 requests per minute (enrollments and identifications together). To raise them, [talk to sales](https://aircaps.ai/contact).
* Errors: `invalid_audio`, `audio_too_short`, `audio_too_long`, `file_too_large`, `no_enrolled_speakers`, `speaker_not_enrolled`, `speaker_not_found`. See [Errors](https://aircaps.ai/docs/errors.md).

## Privacy and consent

Clips are never stored. Speaker profiles (names, metadata and voice embeddings) are kept until you delete them. Voice embeddings can count as biometric data under laws such as the GDPR and Illinois BIPA, so tell the people you enroll and identify, get their consent where required, and delete their profiles when you no longer need them. See [Data & privacy](https://aircaps.ai/docs/data-privacy.md#voice-data-and-consent).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.