A face encoding verification service built with FastAPI. Clients create a container (a verification session), upload up to 5 images, and poll for 128-dimension face encoding vectors extracted asynchronously by an external encoding service.
- Create a container — a logical grouping for one verification attempt
- Upload images — each upload returns immediately with status
"processing" - Poll the summary — encodings appear as the external service processes each image
Image encoding is delegated to an external service. Processing happens in the background — the upload endpoint never blocks on encoding. Failed images store the error and still count toward the 5-image limit. There is no retry mechanism.
erDiagram
containers ||--o{ images : has
images ||--o{ encodings : has
containers {
string id PK "UUID v4"
datetime created_at
}
images {
string id PK "UUID v4"
string container_id FK "CASCADE delete"
enum status "processing | completed | failed"
datetime created_at
string error "null unless failed"
}
encodings {
string id PK "UUID v4 (face_id)"
string image_id FK "CASCADE delete"
blob vector "128 doubles, struct-packed"
}
sequenceDiagram
participant Client
participant API
participant Service
participant DB
participant Encoding as Encoding Service
Client->>API: POST /v1/containers
API->>Service: create_container()
Service->>DB: INSERT container
API-->>Client: 201 {container_id, created_at}
Client->>API: POST /v1/containers/{id}/images
API->>Service: add_image(id, image_data)
Service->>DB: SELECT container (existence check)
Service->>DB: INSERT image (atomic count + insert)
Service-->>API: ImageRecord (status: processing)
API-->>Client: 202 {image_id, status: processing}
Note right of Service: Background task (fire-and-forget)
Service--)Encoding: POST /v1/selfie (image bytes)
Encoding--)Service: 200 [[float]] (face vectors)
Service--)DB: UPDATE image status=completed, INSERT encodings
Client->>API: GET /v1/containers/{id}/summary
API->>Service: get_summary(id)
Service->>DB: SELECT container + images + encodings
API-->>Client: 200 {container_id, images: [...]}
make docker-upThis starts two services:
- app — the Face Encoding API at
http://localhost:8001 - encoding-service — the external face encoding service at
http://localhost:8000
Stop everything:
make docker-downPrerequisites: Python 3.12+, Poetry
poetry install
make run # starts at http://localhost:8001 with hot reloadThe encoding service must be running separately:
docker run -p 8000:8000 veriffdocker/face-encoding-test-task:latestAll endpoints are under /v1/containers. Interactive docs available at http://localhost:8001/docs.
curl -X POST http://localhost:8001/v1/containers{
"container_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"created_at": "2026-03-25T12:00:00Z"
}curl -X POST http://localhost:8001/v1/containers/{container_id}/images \
-F "file=@photo.jpg"{
"image_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"status": "processing",
"created_at": "2026-03-25T12:00:01Z"
}Poll until no images have status: "processing".
curl http://localhost:8001/v1/containers/{container_id}/summary{
"container_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"created_at": "2026-03-25T12:00:00Z",
"images": [
{
"image_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"status": "completed",
"created_at": "2026-03-25T12:00:01Z",
"encodings": [
{
"face_id": "11223344-5566-7788-99aa-bbccddeeff00",
"vector": [0.037, -0.148, 0.020, "... 128 floats total"]
}
],
"error": null
}
]
}All errors use {"error": "message"} format:
| Status | Meaning |
|---|---|
| 400 | No image file provided |
| 404 | Container not found |
| 422 | Container image limit reached (max 5) |
| Environment Variable | Default | Description |
|---|---|---|
ENCODING_SERVICE_URL |
http://localhost:8000/v1/selfie |
External encoding service endpoint |
DATABASE_URL |
sqlite+aiosqlite:///face_encoding.db |
SQLAlchemy database URL |
make test # unit + service + API tests (no Docker needed)
make test-integration # integration tests (starts encoding service via Docker Compose)
make lint # black, isort, flake8, mypy- SQLite — single-file database, no concurrent write scaling. Suitable for the scope of this project.
- No authentication — all endpoints are publicly accessible.
- No retry — if encoding fails, the image is permanently marked as failed. Clients must create a new container to retry.
- No graceful shutdown — background encoding tasks are fire-and-forget. In-flight tasks may be lost on restart.
- No file size limit — uploaded images are read entirely into memory.
- No logging — errors are stored in the database but not logged to stdout/stderr.
- External service trust — encoding vectors from the external service are stored without validation beyond length (128 floats).