> For AI agents: the documentation index is at https://platform.detectif.ai/llms.txt. Every page is also available as Markdown by adding `.md` to its path.

# Detect AI-generated speech

`POST /v1/deepfake/detect`

Checks whether a recording contains AI-generated speech. Send one audio file longer than 4 seconds and at most 120 seconds. Use the result's `verdict`: `real`, `fake`, or `not_sure` when the recording cannot be judged reliably (try a longer or cleaner recording). `score` (0 to 1, higher means more likely AI-generated) is kept for older clients together with `label` and `is_synthetic`. A `not_sure` verdict uses credits like any result, except when the check could not run completely: then `credits.charged` is 0. Main failures: the file is not audio or is damaged (415/422), 4 seconds or shorter or longer than 120 seconds (422), not enough credits (402), the service is busy (503, retry later). Requires scope `deepfake:detect`.

## Authentication

Send your API key as `Authorization: Bearer <your API key>`.

## Headers

| Name | Type | Required | Description |
|---|---|---|---|
| `Authorization` | string | yes | `Bearer <your API key>`. Keys are created in the dashboard; see [Authentication](https://platform.detectif.ai/docs/authentication/). |
| `Idempotency-Key` | string or null | no | 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); send the other fields as form fields.

| Field | Type | Required | Description |
|---|---|---|---|
| `file` | file | yes | The audio file: WAV, MP3, FLAC, OGG/Opus, WebM, M4A/AAC or AIFF, at most 100 MB. |

## Response

`200`: The check completed.

| Field | Type | Required | Description |
|---|---|---|---|
| `request_id` | string | yes |  |
| `operation_id` | string | yes |  |
| `object` | string | yes | (always `"deepfake.detection"`) |
| `result` | object | yes |  |
| `result.verdict` | string | yes | The decision: `real`, `fake` (AI-generated), or `not_sure` (try a longer or cleaner recording). (one of `real`, `fake`, `not_sure`) |
| `result.label` | string | yes | The same decision in the older wording: `synthetic` (fake), `genuine` (real), `uncertain` (not sure). (one of `synthetic`, `genuine`, `uncertain`) |
| `result.is_synthetic` | boolean | yes | true only when the verdict is `fake`. |
| `result.score` | number | yes | How likely the recording is AI-generated, from 0 to 1. (0 to 1) |
| `result.threshold` | number | yes | The main model's threshold (0.62). Informational: decide with `verdict`. |
| `result.score_semantics` | string | yes | A short reminder of what `score` means. |
| `result.duration_seconds` | number | yes | Length of the checked audio, in seconds. |
| `result.segments` | array of object | yes | Scores for consecutive, overlapping parts of the recording. |
| `result.segments.start` | number | yes | Start of this part of the recording, in seconds. |
| `result.segments.end` | number | yes | End of this part of the recording, in seconds. |
| `result.segments.score` | number | yes | How likely this part is synthetic (0 to 1). (0 to 1) |
| `model` | object | yes |  |
| `model.name` | string | yes | Model family: `aletheia` (deepfake detection) or `eidos` (speaker verification). |
| `model.version` | string | yes | Opaque model version; it changes whenever the model is updated. |
| `credits` | object | yes |  |
| `credits.charged` | integer | yes | Credits 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`. |
| `credits.balance` | integer or null | yes | Your available credits after this request; null on the unlimited plan. |
| `credits.original_charge` | integer or null | no | Only on repeated requests (`charged` is then 0): the credits the original request used. |
| `credits.unlimited` | boolean or null | no | Present (true) only for accounts on the unlimited plan, where checks use no credits. |
| `latency_ms` | number | yes |  |
| `idempotent_replay` | boolean | yes |  |

### Example response

```json
{
  "request_id": "req_8f14e45fceea167a5a36dedd",
  "operation_id": "op_6f1ed002ab5595859014ebf0951522d9",
  "object": "deepfake.detection",
  "idempotent_replay": false,
  "result": {
    "threshold": 0.62,
    "score_semantics": "Higher means more likely AI-generated. Decide with the verdict, not the score alone.",
    "verdict": "real",
    "label": "genuine",
    "is_synthetic": false,
    "score": 0.0731,
    "duration_seconds": 6.4,
    "segments": [
      {
        "start": 0,
        "end": 4,
        "score": 0.061
      },
      {
        "start": 2,
        "end": 6.4,
        "score": 0.085
      }
    ]
  }
}
```

## 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 use the shape described in [Errors](https://platform.detectif.ai/docs/errors/), with a stable `error.code`.

## Code samples

```bash
curl "https://platform.detectif.ai/v1/deepfake/detect" \
  -H "Authorization: Bearer $DETECTIF_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "file=@clip.wav"
```

```python
import os
import uuid

import requests

with open("clip.wav", "rb") as f:
    resp = requests.post(
        "https://platform.detectif.ai/v1/deepfake/detect",
        headers={"Authorization": f"Bearer {os.environ['DETECTIF_API_KEY']}", "Idempotency-Key": str(uuid.uuid4())},
        files={"file": f},
        timeout=300,
    )
print(resp.status_code, resp.json())
```

```javascript
import { readFile } from "node:fs/promises";
import { randomUUID } from "node:crypto";

const form = new FormData();
form.append("file", new Blob([await readFile("clip.wav")]), "clip.wav");

const res = await fetch("https://platform.detectif.ai/v1/deepfake/detect", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.DETECTIF_API_KEY}`,
    "Idempotency-Key": randomUUID(),
  },
  body: form,
});
console.log(res.status, await res.json());
```
