API Reference / Speakers
Enroll a voice sample
POST/v1/speakers/enroll
Saves a voice sample for a speaker so that later recordings can be verified against it. The speaker is created on first use. Send a recording of 15-60 seconds with at least 10 seconds of clear speech from that one person. Up to 5 samples per speaker; 2-3 samples from different sessions make verification more reliable. Main failures: too short, too long or too little speech (422, the message says what is needed), the speaker already has 5 samples (409), not enough credits (402). Requires scope speakers:enroll.
Authentication
Send your API key as Authorization: Bearer <your API key>.
Headers
| Name | Type | Details |
|---|---|---|
Authorizationrequired | string |
|
Idempotency-Keyoptional | string or null | Optional (recommended) key that makes retries safe for 24 h: a retry with the same key and the same request returns the stored response without charging again. 1 to 128 characters |
Request body
Send the request as multipart/form-data. Upload the audio file in the file field (up to 100 MB) and send the other fields as form fields. See Audio and limits.
filefilerequiredThe audio file: WAV, MP3, FLAC, OGG/Opus, WebM, M4A/AAC or AIFF, at most 100 MB.
speaker_idstringrequiredYour identifier for the speaker (1-64 characters: letters, digits, _ . : -).
1 to 64 characterspattern
^[A-Za-z0-9_.:-]{1,64}$display_namestringOptional label for the speaker, shown in the dashboard.
at most 128 characters
Response
200 The sample was saved.
request_idstringrequiredoperation_idstringrequiredobjectstringrequiredalways
"speaker.enrollment"speakerobjectrequired5 fields in
speakerspeaker_idstringrequiredpattern
^[A-Za-z0-9_.:-]{1,64}$display_namestring or nullrequiredsamplesintegerrequiredActive enrollment samples for the current speaker model (max 5).
created_atstringrequiredupdated_atstringrequired
qualityobjectrequired2 fields in
qualityspeech_secondsnumberrequiredduration_secondsnumberrequired
modelobjectrequired2 fields in
modelnamestringrequiredModel family:
aletheia(deepfake detection) oreidos(speaker verification).versionstringrequiredOpaque model version; it changes whenever the model is updated.
creditsobjectrequired4 fields in
creditschargedintegerrequiredCredits used by this request. A repeated request with the same Idempotency-Key uses none: it reports 0 here and what the original request used in
original_charge.balanceinteger or nullrequiredYour available credits after this request; null on the unlimited plan.
original_chargeinteger or nullOnly on repeated requests (
chargedis then 0): the credits the original request used.unlimitedboolean or nullPresent (true) only for accounts on the unlimited plan, where checks use no credits.
latency_msnumberrequiredidempotent_replaybooleanrequired
Example response
{
"request_id": "req_8f14e45fceea167a5a36dedd",
"operation_id": "op_6f1ed002ab5595859014ebf0951522d9",
"object": "speaker.enrollment",
"speaker": {
"speaker_id": "customer-4711",
"display_name": "Ada Lovelace",
"samples": 1,
"created_at": "2026-10-13T09:30:00Z",
"updated_at": "2026-10-13T09:30:00Z"
},
"quality": {
"speech_seconds": 17.9,
"duration_seconds": 19.2
},
"idempotent_replay": false
}Errors
| Status | Meaning |
|---|---|
| 400 | The request is invalid, for example a missing or malformed field. |
| 401 | The API key is missing, invalid or revoked. |
| 402 | Not enough credits are available for this operation. |
| 403 | The key lacks the required scope, the email address is not verified, or the account is suspended. |
| 408 | The upload did not arrive in time (the whole file within 120 seconds, at least 64 KB per second). |
| 409 | Conflict, for example a request with the same Idempotency-Key is still running, or the speaker already has the maximum number of samples. |
| 413 | The upload is larger than 100 MB. |
| 415 | Not a supported audio format, or the request is not multipart/form-data. |
| 422 | The audio cannot be used (damaged, too short, too long, silent or too little speech; the message says what is needed), or the Idempotency-Key was already used for a different request. |
| 429 | Too many requests or too many operations at the same time. Wait for the number of seconds in Retry-After. |
| 500 | Something went wrong on our side. No credits were used. |
| 502 | The check failed on our side. No credits were used; retry later. |
| 503 | The service is busy, for example with many large uploads. Retry after the Retry-After seconds. |
| 504 | The check took too long. No credits were used; retry later. |
Error bodies share one shape with a stable error.code; see Errors for every code and how to fix it.
Code samples
curl "https://platform.detectif.ai/v1/speakers/enroll" \
-H "Authorization: Bearer $DETECTIF_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F "speaker_id=alice" \
-F "file=@clip.wav"import os
import uuid
import requests
with open("clip.wav", "rb") as f:
resp = requests.post(
"https://platform.detectif.ai/v1/speakers/enroll",
headers={"Authorization": f"Bearer {os.environ['DETECTIF_API_KEY']}", "Idempotency-Key": str(uuid.uuid4())},
data={"speaker_id": "alice"},
files={"file": f},
timeout=300,
)
print(resp.status_code, resp.json())import { readFile } from "node:fs/promises";
import { randomUUID } from "node:crypto";
const form = new FormData();
form.append("speaker_id", "alice");
form.append("file", new Blob([await readFile("clip.wav")]), "clip.wav");
const res = await fetch("https://platform.detectif.ai/v1/speakers/enroll", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DETECTIF_API_KEY}`,
"Idempotency-Key": randomUUID(),
},
body: form,
});
console.log(res.status, await res.json());