A REST API that classifies a speech clip as human or AI-generated, returning a confidence score and a short explanation.
Built with FastAPI. Speech is represented using wav2vec2 embeddings, which are reduced to a fixed-length vector by statistical pooling and then scored by a lightweight decision function.
- Load — audio is fetched from a URL or decoded from base64, resampled to 16 kHz mono, and truncated to the first 8 seconds.
- Embed —
facebook/wav2vec2-baseproduces a sequence of 768-dimensional hidden states. - Pool — the mean and standard deviation are taken across the time axis and concatenated into a single 1536-dimensional vector. Including the standard deviation matters: synthetic speech tends to vary less across frames than natural speech does.
- Score — the pooled embedding is passed through a calibrated logistic function to produce a probability, which is thresholded at 0.5.
- Guard — clips shorter than one second are returned as
humanwith low confidence rather than being scored, since there is not enough signal to judge.
The current classifier (SimpleSpoofClassifier) is a hand-calibrated logistic curve over
the embedding norm, not a model trained on a labelled spoof dataset. It was chosen to keep
the service dependency-free and fast to deploy. It is a placeholder: the embedding pipeline
above is the reusable part, and swapping in a trained classifier means replacing one
predict_proba method. Treat the current confidence values as indicative, not calibrated
against a benchmark such as ASVspoof.
Both endpoints require authentication and return the same response shape.
Auth is accepted either as an x-api-key header or as Authorization: Bearer <key>.
Takes a publicly reachable audio URL.
{
"audio_url": "https://example.com/sample.wav",
"language": "en"
}Takes inline base64 audio, for callers that cannot host a file.
{
"language": "en",
"audio_format": "wav",
"audio_base64": "<base64-encoded audio>"
}{
"prediction": "ai",
"confidence": 0.732,
"model_version": "v1.0",
"explanation": "Voice embedding shows synthetic speech characteristics"
}prediction is ai or human; confidence is between 0 and 1 and always refers to the
predicted class.
| Status | Meaning |
|---|---|
| 400 | Audio could not be fetched, decoded, or was empty |
| 401 | Missing or incorrect API key |
| 500 | Unexpected server error |
Requires Python 3.11.
pip install -r requirements.txt
cp .env.example .env # then set API_KEY
uvicorn app.main:app --reloadInteractive docs are served at http://127.0.0.1:8000/docs.
| Variable | Description |
|---|---|
API_KEY |
Shared secret required on every request |
MODEL_VERSION |
Version string echoed back in the response |
app/
main.py FastAPI application
api/routes.py endpoint definitions
core/config.py environment configuration
core/security.py API key verification
models/schemas.py Pydantic request/response models
services/
audio_loader.py fetch and decode audio from a URL
audio_base64_loader.py decode inline base64 audio
ml_model.py wav2vec2 embedding extraction
classifier.py scoring function
detector.py orchestration and short-clip guard
Deployed on Railway using a CPU-only PyTorch build. torch.set_num_threads(2) keeps memory
and CPU use within a small instance; the wav2vec2 weights are loaded once at import time
rather than per request.
- The classifier is heuristic, as described above, and has not been evaluated against a labelled spoof dataset.
- Only the first 8 seconds of any clip are considered.
- The
languagefield is accepted but not currently used. - Model weights are downloaded from Hugging Face on first start, so the initial request after a cold deploy is slow.
- Train a real classifier head on an open spoof-detection dataset and report EER/AUC.
- Cache or vendor the model weights to remove the cold-start download.
- Add batching so multiple clips can be scored in one request.