A production-grade REST API wrapping DeepFace — a lightweight face recognition and analysis library for Python.
Built with Flask, Pydantic v2, and Docker.
- Face verification — determine whether two images show the same person
- Face analysis — predict age, gender, emotion, and race
- Face embedding — extract a numerical vector for downstream storage or comparison
- Face search — find matching identities in a directory of reference images
- Face extraction — detect and crop every face in an image
- All three image input formats supported: file path, URL, base64 data-URI
- Structured JSON logs with per-request tracing (
X-Request-Idheader) - Typed configuration via pydantic-settings (
.envfile) - Docker + docker-compose ready
deepface-rest-api/
├── main.py # Entry point (sets CUDA env before TF import)
├── core/
│ ├── factory.py # create_app(), error handlers, request lifecycle
│ ├── config.py # Typed settings via pydantic-settings
│ ├── exceptions.py # Domain exception hierarchy with HTTP codes
│ ├── images.py # managed_image_path() context manager
│ └── logging.py # Structured JSON/text logger + request_id
├── schemas/
│ └── faces.py # Pydantic v2 request & response models
├── services/
│ └── deepface_service.py # Business logic — the only place DeepFace is called
├── api/v1/
│ └── routes.py # Flask Blueprint — HTTP layer only
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── .env.example
.env ──► core/config.py
│
core/exceptions.py core/images.py core/logging.py
│ │ │
schemas/faces.py ──────────────────────────────────────────
│
services/deepface_service.py (calls DeepFace here only)
│
api/v1/routes.py (HTTP: parse → call → respond)
│
app_factory.py (register blueprint, error handlers)
│
main.py (entry point)
git clone https://github.com/DmytroGural/deepface-rest-api.git
cd deepface-rest-api
pip install -r requirements.txt
cp .env.example .env # edit as needed
python main.py
# → http://localhost:5000cp .env.example .env # edit as needed
mkdir -p data/faces_db # or point FACES_DB_PATH to an existing directory
docker compose up --buildFirst run — DeepFace downloads model weights on the first request (~500 MB total).
They are stored in thedeepface_weightsDocker volume and reused on subsequent starts.
All settings are read from the .env file (or environment variables).
| Variable | Default | Description |
|---|---|---|
HOST |
0.0.0.0 |
Bind address |
PORT |
5000 |
Bind port |
DEBUG |
false |
Flask debug mode |
DEFAULT_MODEL |
VGG-Face |
Default recognition model |
DEFAULT_DETECTOR |
opencv |
Default face detector |
DEFAULT_METRIC |
cosine |
Default distance metric |
FACES_DB_PATH |
(empty) | Path to reference faces directory for /find |
GPU_DEVICE |
-1 |
-1 = CPU only, 0 = first GPU, "" = auto |
LOG_LEVEL |
INFO |
DEBUG / INFO / WARNING / ERROR |
LOG_FORMAT |
json |
json (production) or text (development) |
MAX_IMAGE_BYTES |
10485760 |
Maximum image size in bytes (default 10 MB) |
HOST_PORT |
5000 |
Host port mapping for Docker |
All endpoints accept Content-Type: application/json.
Every response includes X-Request-Id and X-Response-Time-Ms headers.
Returns service info and available endpoints.
Liveness probe.
{ "status": "ok", "version": "0.0.1" }Determine whether two images belong to the same person.
Request
{
"img1": "https://example.com/person_a.jpg",
"img2": "https://example.com/person_b.jpg",
"model_name": "VGG-Face",
"detector_backend": "opencv",
"distance_metric": "cosine",
"enforce_detection": true,
"align": true
}Response 200
{
"success": true,
"verified": true,
"distance": 0.2134,
"threshold": 0.4,
"model": "VGG-Face",
"detector_backend": "opencv",
"similarity_metric": "cosine",
"facial_areas": { "img1": {...}, "img2": {...} },
"time": 0.34
}Analyze face attributes. Pass a subset of actions to speed up the response.
Request
{
"img": "https://example.com/face.jpg",
"actions": ["age", "gender", "emotion", "race"]
}Response 200
{
"success": true,
"count": 1,
"result": [
{
"age": 27,
"dominant_gender": "Woman",
"gender": { "Woman": 96.3, "Man": 3.7 },
"dominant_emotion": "happy",
"emotion": { "happy": 95.8, "neutral": 2.2, ... },
"dominant_race": "asian",
"race": { "asian": 78.2, "white": 12.1, ... },
"region": { "x": 10, "y": 20, "w": 120, "h": 120 },
"face_confidence": 0.97
}
]
}Extract a face embedding vector for storage or custom similarity search.
Request
{
"img": "https://example.com/face.jpg",
"model_name": "Facenet512",
"normalization": "base"
}Response 200
{
"success": true,
"count": 1,
"result": [
{
"embedding": [0.023, -0.142, ...],
"facial_area": { "x": 10, "y": 20, "w": 120, "h": 120 },
"face_confidence": 0.98
}
]
}Embedding dimensions by model: VGG-Face → 2622, Facenet → 128, Facenet512 / ArcFace → 512.
Search for matching faces in the configured FACES_DB_PATH directory.
On the first call DeepFace builds an index (
.pklfile) insideFACES_DB_PATH.
Subsequent calls use the cached index and are significantly faster.
Database directory structure
faces_db/
alice/
photo1.jpg
photo2.jpg
bob/
photo1.jpg
Request
{
"img": "https://example.com/query.jpg",
"model_name": "VGG-Face",
"distance_metric": "cosine",
"threshold": 0.4
}Response 200
{
"success": true,
"faces_queried": 1,
"result": [
[
{
"identity": "/data/faces_db/alice/photo1.jpg",
"distance": 0.18,
"threshold": 0.4
}
]
]
}Detect and crop all faces in an image. Cropped faces are returned as base64 PNG strings.
Request
{
"img": "https://example.com/group.jpg",
"detector_backend": "retinaface",
"expand_percentage": 10
}Response 200
{
"success": true,
"faces_found": 2,
"result": [
{
"face_base64": "data:image/png;base64,iVBORw0KGgo...",
"facial_area": { "x": 10, "y": 20, "w": 120, "h": 120 },
"confidence": 0.99
}
]
}All errors share a unified structure:
{
"success": false,
"error_code": "validation_error",
"message": "Request validation failed",
"request_id": "a3f1c2e9b0d4",
"details": {
"fields": [
{ "field": "img1", "msg": "Field required" }
]
}
}error_code |
HTTP | Cause |
|---|---|---|
validation_error |
422 | Invalid JSON or missing/wrong fields |
image_decode_error |
400 | Invalid base64, unreachable URL, or file not found |
image_too_large |
413 | Image exceeds MAX_IMAGE_BYTES |
no_face_detected |
422 | DeepFace could not detect a face |
db_not_found |
400 | FACES_DB_PATH directory does not exist |
service_unavailable |
503 | FACES_DB_PATH is not configured |
deepface_error |
500 | Internal DeepFace error |
internal_error |
500 | Unexpected server error |
not_found |
404 | Route does not exist |
method_not_allowed |
405 | Wrong HTTP method |
| Parameter | Values |
|---|---|
model_name |
VGG-Face, Facenet, Facenet512, OpenFace, DeepFace, DeepID, ArcFace, Dlib, SFace |
detector_backend |
opencv, ssd, dlib, mtcnn, retinaface, mediapipe, yolov8, yunet, fastmtcnn |
distance_metric |
cosine, euclidean, euclidean_l2 |
actions |
age, gender, emotion, race |
normalization |
base, raw, Facenet, Facenet2018, VGGFace, VGGFace2, ArcFace |
| Concern | Solution |
|---|---|
| Configuration | pydantic-settings — typed, validated at startup, reads .env |
| Request validation | Pydantic v2 model_validate — field-level error messages |
| Image handling | managed_image_path() context manager — temp files always cleaned up |
| Error handling | AppError hierarchy with HTTP code baked in — single @errorhandler covers everything |
| Logging | Structured JSON with request_id via contextvars — no need to pass it through function arguments |
| GPU config | CUDA_VISIBLE_DEVICES set before TensorFlow import in main.py |
| Layer separation | core → schemas → services → api — each layer knows only the layer below |
MIT