{"openapi":"3.1.0","info":{"title":"DetectifAI Developer API","description":"The DetectifAI developer API detects synthetic (AI-generated) speech and verifies speakers by voice.\n\n**Authentication.** Send your API key in the `Authorization` header: `Authorization: Bearer dtf_live_...`.\nKeys are created in the dashboard and shown once. Never put keys in URLs.\n\n**Credits.** Detection, enrollment and verification use credits from your account balance; your balance is\nshown in the dashboard and at `GET /v1/credits`. Only completed checks use credits: a rejected file, a busy\nservice or a failed check uses none. A verification with `detect_deepfake=true` counts as two checks.\n\n**Uploads.** Send audio as `multipart/form-data` (field `file`), up to 100 MB. The whole upload must arrive\nwithin 120 s (at no less than 64 KB/s); larger or slower uploads are rejected (`413`, `408`). When too many\nlarge uploads are in progress the API answers `503 server_busy` with `Retry-After`. Durations are checked\nafter decoding: enrollment 15-60 s with at least 10 s of speech, verification 3-60 s with at least 2 s of\nspeech, deepfake detection 0.5-120 s.\n\n**Idempotency.** Send an `Idempotency-Key` header on billable POSTs. Retrying with the same key and the same\nrequest returns the stored response (`idempotent_replay: true`, header `Idempotent-Replayed: true`) without\ncharging again or re-running the model. Keys are remembered for 24 h; results stay available for 30 days at\n`GET /v1/operations/{operation_id}`.\n\n**Errors.** Every non-2xx response uses the same envelope:\n`{\"error\": {\"type\", \"code\", \"message\", \"param\"}, \"request_id\"}`. Every response carries `X-Request-Id`.\n\n**Reading results.** `score` (0 to 1) says how likely a recording is AI-generated and `similarity` (-1 to 1)\nhow alike two voices are. Each result carries the `threshold` used for its decision. Results are strong\nsignals, not proof: try them on your own recordings before relying on them.","version":"1.0.0"},"servers":[{"url":"https://platform.detectif.ai"}],"paths":{"/v1/deepfake/detect":{"post":{"tags":["Deepfake detection"],"summary":"Detect synthetic speech","description":"Checks whether a recording contains AI-generated (synthetic) speech. Send one audio file of 0.5-120 seconds. The result has a `label` (`synthetic` or `genuine`), a `score` from 0 to 1 (higher means more likely synthetic), the `threshold` used for the label, and scores for parts of the recording (`segments`). Main failures: the file is not audio or is damaged (415/422), too short or too long (422), not enough credits (402), the service is busy (503, retry later). Requires scope `deepfake:detect`.","operationId":"detect_deepfake_v1_deepfake_detect_post","security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":128},{"type":"null"}],"description":"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.","examples":["9b1f6c1e-2c1a-4d6e-9a43-2f4f5b6f7a10"],"title":"Idempotency-Key"},"description":"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."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeepfakeResponse"}}}},"400":{"description":"Invalid request parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, invalid or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Not enough credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing scope, unverified email, or inactive account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"408":{"description":"The upload did not arrive in time (whole body within 120 s, at least 64 KB/s).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict (operation in progress, sample limit, ...).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Upload larger than 100 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"Unsupported content type or audio format.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Audio could not be used (corrupt, too short/long, silent, too little speech; the message states the required amounts) or Idempotency-Key reuse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate or concurrency limit exceeded (see Retry-After).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"The check failed on our side; no credits were used. Retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"The service is busy, for example with many large uploads; retry after the Retry-After seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"504":{"description":"The check took too long; no credits were used. Retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"Audio file (WAV, MP3, FLAC, OGG/Opus, WebM, M4A/AAC, AIFF), at most 100 MB."}}}}}}}},"/v1/speakers/enroll":{"post":{"tags":["Speakers"],"summary":"Enroll a speaker sample","description":"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`.","operationId":"enroll_speaker_v1_speakers_enroll_post","security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":128},{"type":"null"}],"description":"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.","examples":["9b1f6c1e-2c1a-4d6e-9a43-2f4f5b6f7a10"],"title":"Idempotency-Key"},"description":"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."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrollResponse"}}}},"400":{"description":"Invalid request parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, invalid or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Not enough credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing scope, unverified email, or inactive account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"408":{"description":"The upload did not arrive in time (whole body within 120 s, at least 64 KB/s).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict (operation in progress, sample limit, ...).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Upload larger than 100 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"Unsupported content type or audio format.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Audio could not be used (corrupt, too short/long, silent, too little speech; the message states the required amounts) or Idempotency-Key reuse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate or concurrency limit exceeded (see Retry-After).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"The check failed on our side; no credits were used. Retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"The service is busy, for example with many large uploads; retry after the Retry-After seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"504":{"description":"The check took too long; no credits were used. Retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file","speaker_id"],"properties":{"file":{"type":"string","format":"binary","description":"Audio file (WAV, MP3, FLAC, OGG/Opus, WebM, M4A/AAC, AIFF), at most 100 MB."},"speaker_id":{"type":"string","pattern":"^[A-Za-z0-9_.:-]{1,64}$","example":"customer-4711"},"display_name":{"type":"string","maxLength":128,"example":"Ada Lovelace"}}}}}}}},"/v1/speakers/verify":{"post":{"tags":["Speakers"],"summary":"Verify a speaker","description":"Checks whether a recording is the voice of an enrolled speaker. Send a recording of 3-60 seconds with at least 2 seconds of speech. The result has a `decision` (`match` or `no_match`), a `similarity` from -1 to 1 (higher means more alike) and the `threshold` used for the decision. Main failures: unknown speaker (404), too short or too little speech (422), not enough credits (402). Requires scope `speakers:verify`.\n\nWith `detect_deepfake=true` the same recording is also checked for AI-generated speech (needs scope `deepfake:detect`). The two checks are counted separately, and only completed checks use credits:\n* if the synthetic-speech check fails, you still get the verification, with `deepfake.error`;\n* if the verification fails but the synthetic-speech check completed, you get the verification's error together with `partial` (the synthetic-speech result);\n* if both fail, you get the error and no credits are used.\n\nA retry with the same `Idempotency-Key` returns the same outcome again without running or counting the checks twice.","operationId":"verify_speaker_v1_speakers_verify_post","security":[{"BearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":128},{"type":"null"}],"description":"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.","examples":["9b1f6c1e-2c1a-4d6e-9a43-2f4f5b6f7a10"],"title":"Idempotency-Key"},"description":"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."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyResponse"}}}},"400":{"description":"Invalid request parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, invalid or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Not enough credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing scope, unverified email, or inactive account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"408":{"description":"The upload did not arrive in time (whole body within 120 s, at least 64 KB/s).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict (operation in progress, sample limit, ...).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyError"}}}},"413":{"description":"Upload larger than 100 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"Unsupported content type or audio format.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Audio could not be used (corrupt, too short/long, silent, too little speech; the message states the required amounts) or Idempotency-Key reuse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate or concurrency limit exceeded (see Retry-After).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"The check failed on our side; no credits were used. Retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyError"}}}},"503":{"description":"The service is busy, for example with many large uploads; retry after the Retry-After seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyError"}}}},"504":{"description":"The check took too long; no credits were used. Retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyError"}}}},"500":{"description":"Internal error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyError"}}}}},"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file","speaker_id"],"properties":{"file":{"type":"string","format":"binary","description":"Audio file (WAV, MP3, FLAC, OGG/Opus, WebM, M4A/AAC, AIFF), at most 100 MB."},"speaker_id":{"type":"string","pattern":"^[A-Za-z0-9_.:-]{1,64}$","example":"customer-4711"},"detect_deepfake":{"type":"boolean","default":false}}}}}}}},"/v1/speakers":{"get":{"tags":["Speakers"],"summary":"List speakers","description":"Speakers of your account, newest first. Requires scope `speakers:read`.","operationId":"list_speakers_v1_speakers_get","security":[{"BearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Page size (1-100).","default":20,"title":"Limit"},"description":"Page size (1-100)."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"description":"`next_cursor` from the previous page.","title":"Cursor"},"description":"`next_cursor` from the previous page."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerList"}}}},"400":{"description":"Invalid request parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, invalid or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing scope, unverified email, or inactive account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate or concurrency limit exceeded (see Retry-After).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/speakers/{speaker_id}":{"get":{"tags":["Speakers"],"summary":"Get a speaker","description":"Requires scope `speakers:read`. Unknown ids return `404 speaker_not_found`.","operationId":"get_speaker_v1_speakers__speaker_id__get","security":[{"BearerAuth":[]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_.:-]{1,64}$","description":"Your identifier for the speaker.","examples":["customer-4711"],"title":"Speaker Id"},"description":"Your identifier for the speaker."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerObject"}}}},"400":{"description":"Invalid request parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, invalid or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing scope, unverified email, or inactive account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found (also returned for resources of other accounts).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate or concurrency limit exceeded (see Retry-After).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Speakers"],"summary":"Delete a speaker","description":"Deletes the speaker and all of its voice samples permanently. Requires scope `speakers:delete`.","operationId":"delete_speaker_v1_speakers__speaker_id__delete","security":[{"BearerAuth":[]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_.:-]{1,64}$","description":"Your identifier for the speaker.","examples":["customer-4711"],"title":"Speaker Id"},"description":"Your identifier for the speaker."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerDeleted"}}}},"400":{"description":"Invalid request parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, invalid or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing scope, unverified email, or inactive account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found (also returned for resources of other accounts).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate or concurrency limit exceeded (see Retry-After).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/operations/{operation_id}":{"get":{"tags":["Operations"],"summary":"Get an operation","description":"Returns the stored outcome of a detection, enrollment or verification (kept 30 days), for example after your connection dropped. Requires the scope of the operation's type.","operationId":"get_operation_v1_operations__operation_id__get","security":[{"BearerAuth":[]}],"parameters":[{"name":"operation_id","in":"path","required":true,"schema":{"type":"string","description":"Operation id (`op_...`).","examples":["op_6f1ed002ab5595859014ebf0951522d9"],"title":"Operation Id"},"description":"Operation id (`op_...`)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationObject"}}}},"401":{"description":"Missing, invalid or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing scope, unverified email, or inactive account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found (also returned for resources of other accounts).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate or concurrency limit exceeded (see Retry-After).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/credits":{"get":{"tags":["Credits & usage"],"summary":"Get credit balance","description":"Your available credits and the credits held by checks that are still running. Requires scope `credits:read`.","operationId":"get_credits_v1_credits_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditsObject"}}}},"400":{"description":"Invalid request parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, invalid or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing scope, unverified email, or inactive account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate or concurrency limit exceeded (see Retry-After).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"BearerAuth":[]}]}},"/v1/usage":{"get":{"tags":["Credits & usage"],"summary":"List usage events","description":"One entry per detection, enrollment or verification request and its outcome, newest first. Requires scope `usage:read`.","operationId":"get_usage_v1_usage_get","security":[{"BearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Cursor"}},{"name":"operation","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: deepfake.detect, speaker.enroll or speaker.verify.","title":"Operation"},"description":"Filter: deepfake.detect, speaker.enroll or speaker.verify."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: succeeded, rejected, failed or replayed.","title":"Status"},"description":"Filter: succeeded, rejected, failed or replayed."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageList"}}}},"400":{"description":"Invalid request parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, invalid or revoked credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing scope, unverified email, or inactive account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate or concurrency limit exceeded (see Retry-After).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/health":{"get":{"tags":["System"],"summary":"Service health","description":"Whether the service is up. No authentication needed.","operationId":"health_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}},"429":{"description":"Rate or concurrency limit exceeded (see Retry-After).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"schemas":{"CreditsCharged":{"properties":{"charged":{"type":"integer","title":"Charged","description":"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`."},"balance":{"type":"integer","title":"Balance","description":"Your available credits after this request."},"original_charge":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Original Charge","description":"Only on repeated requests (`charged` is then 0): the credits the original request used."}},"type":"object","required":["charged","balance"],"title":"CreditsCharged"},"CreditsObject":{"properties":{"request_id":{"type":"string","title":"Request Id"},"object":{"type":"string","const":"credits","title":"Object"},"available":{"type":"integer","title":"Available"},"reserved":{"type":"integer","title":"Reserved","description":"Credits held by checks that are still running."},"price_per_operation":{"type":"integer","title":"Credits per check","description":"Credits one completed check uses."},"plan":{"type":"string","title":"Plan"}},"type":"object","required":["request_id","object","available","reserved","price_per_operation","plan"],"title":"CreditsObject"},"DeepfakeResponse":{"properties":{"request_id":{"type":"string","title":"Request Id"},"operation_id":{"type":"string","title":"Operation Id"},"object":{"type":"string","const":"deepfake.detection","title":"Object"},"result":{"$ref":"#/components/schemas/DeepfakeResult"},"model":{"$ref":"#/components/schemas/ModelInfo"},"credits":{"$ref":"#/components/schemas/CreditsCharged"},"latency_ms":{"type":"number","title":"Latency Ms"},"idempotent_replay":{"type":"boolean","title":"Idempotent Replay"}},"type":"object","required":["request_id","operation_id","object","result","model","credits","latency_ms","idempotent_replay"],"title":"DeepfakeResponse","examples":[{"credits":{"balance":4950,"charged":50},"idempotent_replay":false,"latency_ms":412.7,"model":{"name":"aletheia-wavlm","version":"wavlm-distill-v2-tilepad-epoch063"},"object":"deepfake.detection","operation_id":"op_6f1ed002ab5595859014ebf0951522d9","request_id":"req_8f14e45fceea167a5a36dedd","result":{"duration_seconds":6.4,"is_synthetic":false,"label":"genuine","score":0.0731,"score_semantics":"Higher means more likely AI-generated. A signal, not proof.","segments":[{"end":4.0,"score":0.061,"start":0.0},{"end":6.0,"score":0.085,"start":2.0}],"threshold":0.62}}]},"DeepfakeResult":{"properties":{"label":{"type":"string","enum":["synthetic","genuine"],"title":"Label","description":"`synthetic` when `score` is at or above `threshold`."},"is_synthetic":{"type":"boolean","title":"Is Synthetic"},"score":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Score","description":"How likely the recording is AI-generated, from 0 (genuine) to 1 (synthetic)."},"threshold":{"type":"number","title":"Threshold","description":"The score from which a recording is labelled synthetic."},"score_semantics":{"type":"string","title":"Score Semantics","description":"A short reminder of what `score` means."},"duration_seconds":{"type":"number","title":"Duration Seconds","description":"Length of the checked audio, in seconds."},"segments":{"items":{"$ref":"#/components/schemas/Segment"},"type":"array","title":"Segments","description":"Scores for consecutive parts of the recording."}},"type":"object","required":["label","is_synthetic","score","threshold","score_semantics","duration_seconds","segments"],"title":"DeepfakeResult"},"EmbeddedDeepfake":{"properties":{"label":{"type":"string","enum":["synthetic","genuine"],"title":"Label","description":"`synthetic` when `score` is at or above `threshold`."},"is_synthetic":{"type":"boolean","title":"Is Synthetic"},"score":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Score","description":"How likely the recording is AI-generated, from 0 (genuine) to 1 (synthetic)."},"threshold":{"type":"number","title":"Threshold","description":"The score from which a recording is labelled synthetic."},"score_semantics":{"type":"string","title":"Score Semantics","description":"A short reminder of what `score` means."},"duration_seconds":{"type":"number","title":"Duration Seconds","description":"Length of the checked audio, in seconds."},"segments":{"items":{"$ref":"#/components/schemas/Segment"},"type":"array","title":"Segments","description":"Scores for consecutive parts of the recording."},"operation_id":{"type":"string","title":"Operation Id"},"credits_charged":{"type":"integer","title":"Credits Charged"},"model":{"$ref":"#/components/schemas/ModelInfo"}},"type":"object","required":["label","is_synthetic","score","threshold","score_semantics","duration_seconds","segments","operation_id","credits_charged","model"],"title":"EmbeddedDeepfake"},"EmbeddedDeepfakeError":{"properties":{"error":{"$ref":"#/components/schemas/ErrorDetail"},"operation_id":{"type":"string","title":"Operation Id"},"credits_charged":{"type":"integer","title":"Credits Charged","default":0}},"type":"object","required":["error","operation_id"],"title":"EmbeddedDeepfakeError"},"EnrollQuality":{"properties":{"speech_seconds":{"type":"number","title":"Speech Seconds"},"duration_seconds":{"type":"number","title":"Duration Seconds"}},"type":"object","required":["speech_seconds","duration_seconds"],"title":"EnrollQuality"},"EnrollResponse":{"properties":{"request_id":{"type":"string","title":"Request Id"},"operation_id":{"type":"string","title":"Operation Id"},"object":{"type":"string","const":"speaker.enrollment","title":"Object"},"speaker":{"$ref":"#/components/schemas/Speaker"},"quality":{"$ref":"#/components/schemas/EnrollQuality"},"model":{"$ref":"#/components/schemas/ModelInfo"},"credits":{"$ref":"#/components/schemas/CreditsCharged"},"latency_ms":{"type":"number","title":"Latency Ms"},"idempotent_replay":{"type":"boolean","title":"Idempotent Replay"}},"type":"object","required":["request_id","operation_id","object","speaker","quality","model","credits","latency_ms","idempotent_replay"],"title":"EnrollResponse","examples":[{"credits":{"balance":4900,"charged":50},"idempotent_replay":false,"latency_ms":388.1,"model":{"name":"eidos-redimnet2","version":"redimnet2-b2"},"object":"speaker.enrollment","operation_id":"op_6f1ed002ab5595859014ebf0951522d9","quality":{"duration_seconds":9.2,"speech_seconds":7.9},"request_id":"req_8f14e45fceea167a5a36dedd","speaker":{"created_at":"2026-10-12T09:30:00Z","display_name":"Ada Lovelace","samples":1,"speaker_id":"customer-4711","updated_at":"2026-10-12T09:30:00Z"}}]},"ErrorDetail":{"properties":{"type":{"type":"string","enum":["invalid_request","authentication","permission","not_found","conflict","rate_limited","insufficient_credits","payload_too_large","unsupported_media","unprocessable_audio","overloaded","internal"],"title":"Type"},"code":{"type":"string","title":"Code","description":"Stable snake_case error code."},"message":{"type":"string","title":"Message","description":"Human-readable explanation. Do not parse."},"param":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Param","description":"The request field the error refers to, if any."}},"type":"object","required":["type","code","message"],"title":"ErrorDetail"},"ErrorResponse":{"properties":{"error":{"$ref":"#/components/schemas/ErrorDetail"},"request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Id"}},"additionalProperties":true,"type":"object","required":["error","request_id"],"title":"ErrorResponse","examples":[{"error":{"code":"insufficient_credits","message":"Not enough credits are available for this operation.","type":"insufficient_credits"},"request_id":"req_8f14e45fceea167a5a36dedd"}]},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"request_id":{"type":"string","title":"Request Id"},"status":{"type":"string","enum":["ok","degraded"],"title":"Status"},"version":{"type":"string","title":"Version"},"database":{"type":"string","enum":["ok","unavailable"],"title":"Database"},"inference":{"type":"string","enum":["ok","unavailable"],"title":"Inference"}},"type":"object","required":["request_id","status","version","database","inference"],"title":"HealthResponse"},"ModelInfo":{"properties":{"name":{"type":"string","title":"Name"},"version":{"type":"string","title":"Version"}},"type":"object","required":["name","version"],"title":"ModelInfo"},"OperationObject":{"properties":{"request_id":{"type":"string","title":"Request Id"},"object":{"type":"string","const":"operation","title":"Object"},"operation_id":{"type":"string","title":"Operation Id"},"type":{"type":"string","enum":["deepfake.detect","speaker.enroll","speaker.verify"],"title":"Type"},"status":{"type":"string","enum":["pending","reserved","running","succeeded","failed"],"title":"Status"},"source":{"type":"string","enum":["api","dashboard","mcp"],"title":"Source"},"parent_operation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Operation Id"},"credits_charged":{"type":"integer","title":"Credits Charged"},"created_at":{"type":"string","title":"Created At"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At"},"result":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Result","description":"The original success response body (without request_id)."},"error":{"anyOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"null"}]},"partial":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Partial","description":"For a failed verification whose synthetic-speech check completed: that result."}},"type":"object","required":["request_id","object","operation_id","type","status","source","parent_operation_id","credits_charged","created_at","completed_at","result","error"],"title":"OperationObject"},"PartialOutcome":{"properties":{"deepfake":{"$ref":"#/components/schemas/EmbeddedDeepfake","description":"The synthetic-speech check, which completed."},"credits":{"$ref":"#/components/schemas/CreditsCharged"}},"type":"object","required":["deepfake","credits"],"title":"PartialOutcome"},"Segment":{"properties":{"start":{"type":"number","title":"Start","description":"Start of this part of the recording, in seconds."},"end":{"type":"number","title":"End","description":"End of this part of the recording, in seconds."},"score":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Score","description":"How likely this part is synthetic (0 to 1)."}},"type":"object","required":["start","end","score"],"title":"Segment"},"Speaker":{"properties":{"speaker_id":{"type":"string","pattern":"^[A-Za-z0-9_.:-]{1,64}$","title":"Speaker Id"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"samples":{"type":"integer","title":"Samples","description":"Active enrollment samples for the current speaker model (max 5)."},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["speaker_id","display_name","samples","created_at","updated_at"],"title":"Speaker"},"SpeakerDeleted":{"properties":{"request_id":{"type":"string","title":"Request Id"},"object":{"type":"string","const":"speaker.deleted","title":"Object"},"speaker_id":{"type":"string","title":"Speaker Id"},"deleted":{"type":"boolean","const":true,"title":"Deleted"}},"type":"object","required":["request_id","object","speaker_id","deleted"],"title":"SpeakerDeleted"},"SpeakerList":{"properties":{"request_id":{"type":"string","title":"Request Id"},"object":{"type":"string","const":"list","title":"Object"},"data":{"items":{"$ref":"#/components/schemas/Speaker"},"type":"array","title":"Data"},"has_more":{"type":"boolean","title":"Has More"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["request_id","object","data","has_more","next_cursor"],"title":"SpeakerList"},"SpeakerObject":{"properties":{"speaker_id":{"type":"string","pattern":"^[A-Za-z0-9_.:-]{1,64}$","title":"Speaker Id"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"samples":{"type":"integer","title":"Samples","description":"Active enrollment samples for the current speaker model (max 5)."},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"},"request_id":{"type":"string","title":"Request Id"},"object":{"type":"string","const":"speaker","title":"Object"}},"type":"object","required":["speaker_id","display_name","samples","created_at","updated_at","request_id","object"],"title":"SpeakerObject"},"UsageEvent":{"properties":{"id":{"type":"string","title":"Id"},"operation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operation Id"},"type":{"type":"string","enum":["deepfake.detect","speaker.enroll","speaker.verify"],"title":"Type"},"source":{"type":"string","enum":["api","dashboard","mcp"],"title":"Source"},"status":{"type":"string","enum":["succeeded","rejected","failed","replayed"],"title":"Status"},"http_status":{"type":"integer","title":"Http Status"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code"},"credits_charged":{"type":"integer","title":"Credits Charged"},"duration_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Duration Seconds"},"latency_ms":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latency Ms"},"request_id":{"type":"string","title":"Request Id"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","operation_id","type","source","status","http_status","error_code","credits_charged","duration_seconds","latency_ms","request_id","created_at"],"title":"UsageEvent"},"UsageList":{"properties":{"request_id":{"type":"string","title":"Request Id"},"object":{"type":"string","const":"list","title":"Object"},"data":{"items":{"$ref":"#/components/schemas/UsageEvent"},"type":"array","title":"Data"},"has_more":{"type":"boolean","title":"Has More"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["request_id","object","data","has_more","next_cursor"],"title":"UsageList"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VerifyError":{"properties":{"error":{"$ref":"#/components/schemas/ErrorDetail"},"request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Id"},"operation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operation Id","description":"The (failed) verification operation."},"partial":{"anyOf":[{"$ref":"#/components/schemas/PartialOutcome"},{"type":"null"}],"description":"Present when detect_deepfake=true and the synthetic-speech check completed."}},"additionalProperties":true,"type":"object","required":["error","request_id"],"title":"VerifyError","description":"Error envelope of a verification; with ``detect_deepfake=true`` it may carry ``partial``.","examples":[{"error":{"code":"inference_overloaded","message":"The model service is at capacity; retry shortly.","type":"overloaded"},"operation_id":"op_6f1ed002ab5595859014ebf0951522d9","partial":{"credits":{"balance":4850,"charged":50},"deepfake":{"credits_charged":50,"duration_seconds":4.1,"is_synthetic":false,"label":"genuine","model":{"name":"aletheia-wavlm","version":"wavlm-distill-v2-tilepad-epoch063"},"operation_id":"op_0b9d2f4c1e7a4b6c8d0e1f2a3b4c5d6e","score":0.08,"score_semantics":"Higher means more likely AI-generated. A signal, not proof.","segments":[{"end":4.0,"score":0.08,"start":0.0}],"threshold":0.62}},"request_id":"req_8f14e45fceea167a5a36dedd"}]},"VerifyResponse":{"properties":{"request_id":{"type":"string","title":"Request Id"},"operation_id":{"type":"string","title":"Operation Id"},"object":{"type":"string","const":"speaker.verification","title":"Object"},"speaker_id":{"type":"string","title":"Speaker Id"},"result":{"$ref":"#/components/schemas/VerifyResult"},"deepfake":{"anyOf":[{"$ref":"#/components/schemas/EmbeddedDeepfake"},{"$ref":"#/components/schemas/EmbeddedDeepfakeError"},{"type":"null"}],"title":"Deepfake","description":"null unless detect_deepfake=true; then either the synthetic-speech result or its error."},"model":{"$ref":"#/components/schemas/ModelInfo"},"credits":{"$ref":"#/components/schemas/CreditsCharged"},"latency_ms":{"type":"number","title":"Latency Ms"},"idempotent_replay":{"type":"boolean","title":"Idempotent Replay"}},"type":"object","required":["request_id","operation_id","object","speaker_id","result","deepfake","model","credits","latency_ms","idempotent_replay"],"title":"VerifyResponse","examples":[{"credits":{"balance":4850,"charged":50},"idempotent_replay":false,"latency_ms":301.5,"model":{"name":"eidos-redimnet2","version":"redimnet2-b2"},"object":"speaker.verification","operation_id":"op_6f1ed002ab5595859014ebf0951522d9","request_id":"req_8f14e45fceea167a5a36dedd","result":{"decision":"match","duration_seconds":4.1,"similarity":0.7412,"similarity_semantics":"Higher means the two voices are more alike. Not a percentage.","threshold":0.53},"speaker_id":"customer-4711"}]},"VerifyResult":{"properties":{"decision":{"type":"string","enum":["match","no_match"],"title":"Decision","description":"`match` when `similarity` is at or above `threshold`."},"similarity":{"type":"number","maximum":1.0,"minimum":-1.0,"title":"Similarity","description":"How alike the two voices are, from -1 to 1 (higher is more alike)."},"threshold":{"type":"number","title":"Threshold","description":"The similarity from which the decision is `match`."},"similarity_semantics":{"type":"string","title":"Similarity Semantics","description":"A short reminder of what `similarity` means."},"duration_seconds":{"type":"number","title":"Duration Seconds","description":"Length of the checked audio, in seconds."}},"type":"object","required":["decision","similarity","threshold","similarity_semantics","duration_seconds"],"title":"VerifyResult"}},"securitySchemes":{"BearerAuth":{"type":"http","description":"Send an API key as `Authorization: Bearer dtf_live_...`. The dashboard sends the Supabase session access token the same way. Keys in URLs (`?api_key=`) are rejected.","scheme":"bearer","bearerFormat":"dtf_live_<43 base62 chars> | Supabase access token"}}},"tags":[{"name":"Deepfake detection","description":"Detect synthetic speech in an audio file."},{"name":"Speakers","description":"Enroll speakers and verify them by voice."},{"name":"Operations","description":"Retrieve stored results of model operations."},{"name":"Credits & usage","description":"Balance and per-request usage."},{"name":"System","description":"Health."}]}