Deepfake detection
Aletheia · Deepfake detection answers one question: does this recording contain synthetic speech? It returns a score for the whole recording, a label derived from a threshold, and a score for each window of the recording.
POST /v1/deepfake/detect| Scope | deepfake:detect |
| Cost | 50 credits per successful detection |
| Body | multipart/form-data with one field, file |
| Audio | 0.5 to 120 seconds after decoding, upload up to 20 MB |
| Headers | Authorization (required), Idempotency-Key (recommended) |
How the audio is processed
The server decodes the upload, converts it to 16 kHz mono and checks its length before any credits are reserved. The model scores the audio in 4 second windows with a 2 second hop. The recording's score is the mean of the window scores. Each window is reported in segments.
The audio is processed in memory and temporary storage only and is not kept after the request. See Data retention and privacy.
The model
The model is aletheia-wavlm: a WavLM-base-plus student model fine-tuned on English and Hindi speech. The current version is wavlm-finetune-n-m43-moredata-ep063. Each response names the version that produced it in model.version; read it from there rather than hard-coding it.
Language. The default threshold (0.62) was fitted on English speech. For speech in other languages, including Hindi, results at the default threshold are less reliable. If you score non-English audio, measure scores on your own recordings before acting on the label.
Request
curl https://platform.detectif.ai/v1/deepfake/detect \
-H "Authorization: Bearer $DETECTIF_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F "file=@call-recording.wav"import os
import uuid
import requests
def detect_deepfake(path: str, idempotency_key: str | None = None) -> dict:
with open(path, "rb") as f:
resp = requests.post(
"https://platform.detectif.ai/v1/deepfake/detect",
headers={
"Authorization": f"Bearer {os.environ['DETECTIF_API_KEY']}",
"Idempotency-Key": idempotency_key or str(uuid.uuid4()),
},
files={"file": (os.path.basename(path), f)},
timeout=120,
)
body = resp.json()
if not resp.ok:
err = body["error"]
raise RuntimeError(f"{resp.status_code} {err['code']}: {err['message']} ({body['request_id']})")
return body
body = detect_deepfake("call-recording.wav")
result = body["result"]
print(f"{result['label']} score={result['score']:.3f} threshold={result['threshold']}")
for seg in result["segments"]:
print(f" {seg['start']:6.1f}s to {seg['end']:6.1f}s {seg['score']:.3f}")import { readFile } from "node:fs/promises";
import { basename } from "node:path";
import { randomUUID } from "node:crypto";
async function detectDeepfake(path, idempotencyKey = randomUUID()) {
const form = new FormData();
form.append("file", new Blob([await readFile(path)]), basename(path));
const res = await fetch("https://platform.detectif.ai/v1/deepfake/detect", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DETECTIF_API_KEY}`,
"Idempotency-Key": idempotencyKey,
},
body: form,
});
const body = await res.json();
if (!res.ok) {
throw new Error(`${res.status} ${body.error.code}: ${body.error.message} (${body.request_id})`);
}
return body;
}
const { result } = await detectDeepfake("call-recording.wav");
console.log(`${result.label} score=${result.score.toFixed(3)} threshold=${result.threshold}`);
for (const seg of result.segments) {
console.log(` ${seg.start}s to ${seg.end}s ${seg.score.toFixed(3)}`);
}Response
Example response (values are illustrative):
{
"request_id": "req_2c41d7e0b9a8b0ef21af017f",
"operation_id": "op_9a0f3b6c1e72d8e6725b71fcb5f62c89",
"object": "deepfake.detection",
"result": {
"label": "synthetic",
"is_synthetic": true,
"score": 0.91,
"threshold": 0.62,
"score_semantics": "model output probability of synthetic speech; not calibrated to your traffic",
"duration_seconds": 9.2,
"segments": [
{ "start": 0.0, "end": 4.0, "score": 0.88 },
{ "start": 2.0, "end": 6.0, "score": 0.93 },
{ "start": 4.0, "end": 8.0, "score": 0.92 },
{ "start": 6.0, "end": 9.2, "score": 0.90 }
]
},
"model": { "name": "aletheia-wavlm", "version": "wavlm-finetune-n-m43-moredata-ep063" },
"credits": { "charged": 50, "balance": 99900 },
"latency_ms": 388.1,
"idempotent_replay": false
}| Field | Description |
|---|---|
request_id |
Identifier of this HTTP request. Also in the X-Request-Id header. Include it when you contact support. |
operation_id |
Identifier of the stored result. Fetch it again for free with GET /v1/operations/{operation_id}. |
result.score |
Model output between 0 and 1. Higher means the model found more evidence of synthetic speech. |
result.threshold |
The decision threshold applied to score for this request. |
result.label |
synthetic when score is at or above threshold, otherwise genuine. |
result.is_synthetic |
true when label is synthetic. |
result.score_semantics |
A plain statement of what the score means. |
result.duration_seconds |
Length of the decoded audio. |
result.segments |
One entry per window: start and end in seconds and that window's score. |
model.name, model.version |
The model that produced the result. Store these with your decision. |
credits.charged |
Credits charged for this request: 50, or 0 for an idempotent replay. A replay also has credits.original_charge, the amount charged for the original request. |
credits.balance |
Available credits after this request. |
latency_ms |
Server-side processing time for this request. It varies with load and is not a service level. |
idempotent_replay |
true when this is a stored response returned for a repeated Idempotency-Key. |
Reading the result
- The score is not calibrated to your traffic. It is the model's output probability, not a measured error rate for your callers, devices or codecs. Before you act on scores automatically, measure them on recordings from your own channels.
- Read the threshold from each response. The default is 0.62, fitted on English speech, and it can change. Do not hard-code it.
- Other languages are less reliable. Results for speech that is not English, including Hindi, are less reliable at the default threshold.
- Short clips are less reliable. Scores for clips of 2 seconds or less are less reliable than for longer recordings. The minimum accepted length is 0.5 seconds.
- A
syntheticlabel is not proof of fraud, andgenuineis not a guarantee. Treat the result as one finding. Leave room for another check or human review, for example "More evidence needed. Request a second check before proceeding." - Use
segmentsto find where. A recording can contain both genuine and synthetic speech. A short run of high window scores in a long recording is worth reviewing even when the overall score is below the threshold. - Authenticity and identity are separate findings. A genuine score says nothing about who is speaking. To check identity as well, use speaker verification with
detect_deepfake=true.
Errors
The common errors for this endpoint are below. Requests that fail are not charged. See Errors for the full list.
| HTTP | error.code |
What to do |
|---|---|---|
| 413 | file_too_large |
Upload 20 MB or less. |
| 415 | unsupported_format |
Send WAV, MP3, FLAC, OGG/Opus, WebM, M4A/AAC or AIFF audio. |
| 422 | corrupt_audio |
The file could not be decoded. Check the file. |
| 422 | audio_too_short, audio_too_long |
Send 0.5 to 120 seconds of audio. |
| 422 | silent_audio |
The recording contains no signal. |
| 402 | insufficient_credits |
Your balance is below 50 credits. |
| 429 | rate_limited, concurrency_limited |
Wait for Retry-After seconds, then retry with the same Idempotency-Key. |
| 503 | inference_overloaded |
Wait for Retry-After seconds, then retry with the same Idempotency-Key. |
| 504 | inference_timeout |
Retry with the same Idempotency-Key. |
Fetching a stored result
If your connection drops after the request was accepted, the detection still completes and is stored. Fetch it with the operation_id (free), or repeat the request with the same Idempotency-Key to receive the stored response without a second charge.
curl https://platform.detectif.ai/v1/operations/op_9a0f3b6c1e72d8e6725b71fcb5f62c89 \
-H "Authorization: Bearer $DETECTIF_API_KEY"import os
import requests
resp = requests.get(
"https://platform.detectif.ai/v1/operations/op_9a0f3b6c1e72d8e6725b71fcb5f62c89",
headers={"Authorization": f"Bearer {os.environ['DETECTIF_API_KEY']}"},
timeout=30,
)
print(resp.json())const res = await fetch("https://platform.detectif.ai/v1/operations/op_9a0f3b6c1e72d8e6725b71fcb5f62c89", {
headers: { Authorization: `Bearer ${process.env.DETECTIF_API_KEY}` },
});
console.log(await res.json());Stored results can be fetched for 30 days.