Xylolabs Face API Reference¶
Base URL: https://face-api.xylolabs.com
Interactive docs: /docs (Swagger UI) | /redoc (ReDoc)
Authentication¶
API Key (Public Endpoints)¶
When FACE_API_API_KEY_ENABLED=true, all public endpoints (/api/v1/detect, /api/v1/mask) require an API key via the X-API-Key header.
API keys are managed through the Admin API. Each key tracks usage metrics (request count, last used timestamp).
API key authentication is enabled by default. All public endpoints require a valid key.
HTTP Basic Auth (Admin Endpoints)¶
All /api/v1/admin/* endpoints require HTTP Basic Authentication.
Credentials are configured via FACE_API_ADMIN_USER and FACE_API_ADMIN_PASSWORD environment variables. Failed login attempts are tracked per IP and blocked after 10 consecutive failures.
Rate Limiting¶
| Endpoint | Default Limit | Config Variable |
|---|---|---|
POST /api/v1/detect |
300/minute | FACE_API_RATE_LIMIT_DETECT |
POST /api/v1/mask |
150/minute | FACE_API_RATE_LIMIT_MASK |
Rate limiting is per-IP. When exceeded, the API returns 429 Too Many Requests with a Retry-After header.
Public Endpoints¶
GET /health¶
Health check. No authentication required.
Response:
{
"status": "ok",
"model": "scrfd_10g",
"model_loaded": true,
"input_size": [640, 640],
"supports_landmarks": true,
"storage_enabled": false
}
Example¶
POST /api/v1/detect¶
Detect faces in an image. Returns JSON with bounding boxes, landmarks, and confidence scores.
Content-Type: multipart/form-data
Request Parameters:
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
image |
file | required | — | Image file (JPEG, PNG, WebP, BMP, TIFF) |
confidence |
float | 0.5 | 0.01–1.0 | Minimum detection confidence threshold |
nms_threshold |
float | 0.4 | 0.01–1.0 | Non-Maximum Suppression IoU threshold |
max_faces |
int | 0 | ≥ 0 | Maximum faces to return (0 = unlimited) |
Response (DetectResponse):
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"image_id": null,
"job_id": null,
"image": {
"width": 1920,
"height": 1080,
"channels": 3,
"size_bytes": 245760
},
"detections": [
{
"index": 0,
"bbox": { "x1": 100.5, "y1": 200.3, "x2": 300.1, "y2": 400.7 },
"confidence": 0.9876,
"landmarks": {
"left_eye": { "x": 150.2, "y": 250.1 },
"right_eye": { "x": 250.3, "y": 248.9 },
"nose": { "x": 200.1, "y": 310.5 },
"left_mouth": { "x": 160.7, "y": 360.2 },
"right_mouth": { "x": 240.5, "y": 358.8 }
},
"size": { "width": 199.6, "height": 200.4 },
"area": 39999.84,
"center": { "x": 200.3, "y": 300.5 },
"relative": {
"x_center": 0.1043,
"y_center": 0.2782,
"width": 0.104,
"height": 0.1855
}
}
],
"summary": {
"face_count": 1,
"avg_confidence": 0.9876,
"min_confidence": 0.9876,
"max_confidence": 0.9876,
"total_face_area": 39999.84,
"face_area_ratio": 0.019
},
"processing": {
"model": "scrfd_10g",
"input_size": [640, 640],
"confidence_threshold": 0.5,
"nms_threshold": 0.4,
"inference_ms": 12.34,
"total_ms": 18.56
},
"profiling": {
"decode_ms": 3.21,
"preprocess_ms": 1.05,
"blob_ms": 0.82,
"inference_ms": 12.34,
"postprocess_ms": 0.45,
"nms_ms": 0.12,
"mask_ms": null,
"encode_ms": null,
"storage_ms": null,
"total_ms": 18.56,
"candidates_before_nms": 15,
"candidates_after_nms": 1,
"det_scale": 0.333333,
"input_resolution": "1920x1080",
"det_resolution": "640x640",
"input_bytes": 245760,
"output_bytes": null,
"input_pixels": 2073600,
"model_name": "scrfd_10g",
"model_size_mb": 16.1,
"onnx_runtime_version": "1.21.0",
"opencv_version": "4.11.0",
"python_version": "3.14.3",
"worker_pid": 12345
}
}
Examples¶
curl:
curl -X POST https://face-api.xylolabs.com/api/v1/detect \
-H "X-API-Key: xyl_your_api_key_here" \
-F "image=@photo.jpg" \
-F "confidence=0.6" \
-F "max_faces=5"
Python (requests):
import requests
response = requests.post(
"https://face-api.xylolabs.com/api/v1/detect",
headers={"X-API-Key": "xyl_your_api_key_here"},
files={"image": open("photo.jpg", "rb")},
params={"confidence": 0.6, "max_faces": 5},
)
data = response.json()
print(f"Found {data['summary']['face_count']} face(s)")
for face in data["detections"]:
bbox = face["bbox"]
print(f" Face #{face['index']}: confidence={face['confidence']:.4f}, "
f"bbox=({bbox['x1']:.0f},{bbox['y1']:.0f})-({bbox['x2']:.0f},{bbox['y2']:.0f})")
JavaScript (fetch):
const formData = new FormData();
formData.append("image", fileInput.files[0]);
const response = await fetch(
"https://face-api.xylolabs.com/api/v1/detect?confidence=0.6",
{
method: "POST",
headers: { "X-API-Key": "xyl_your_api_key_here" },
body: formData,
}
);
const data = await response.json();
console.log(`Found ${data.summary.face_count} face(s)`);
Python (binary bytes upload):
import requests
with open("photo.jpg", "rb") as f:
image_bytes = f.read()
response = requests.post(
"https://face-api.xylolabs.com/api/v1/detect",
headers={"X-API-Key": "xyl_your_api_key_here"},
files={"image": ("photo.jpg", image_bytes, "image/jpeg")},
)
POST /api/v1/mask¶
Detect and mask (blur/pixelate) faces in an image. Returns the processed image as binary data.
Content-Type: multipart/form-data
Request Parameters:
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
image |
file | required | — | Image file (JPEG, PNG, WebP, BMP, TIFF) |
confidence |
float | 0.5 | 0.01–1.0 | Minimum detection confidence |
nms_threshold |
float | 0.4 | 0.01–1.0 | NMS IoU threshold |
max_faces |
int | 0 | ≥ 0 | Maximum faces to mask (0 = all) |
method |
string | gaussian |
gaussian, pixelate, solid, elliptical |
Masking method |
strength |
int | 199 | 1–999 | Blur kernel size or pixelation block size |
padding |
float | 0.15 | 0.0–2.0 | Expand bounding box by this fraction before masking |
format |
string | jpeg |
jpeg, png, webp |
Output image format |
quality |
int | 95 | 1–100 | Output compression quality |
Response: Raw image bytes with metadata headers:
| Header | Description |
|---|---|
X-Request-Id |
Unique request UUID |
X-Face-Count |
Number of faces masked |
X-Decode-Ms |
Image decode time (ms) |
X-Inference-Ms |
Model inference time (ms) |
X-Mask-Ms |
Masking time (ms) |
X-Encode-Ms |
Output encoding time (ms) |
X-Total-Ms |
Total processing time (ms) |
X-Model |
Model name |
X-Image-Width |
Original image width |
X-Image-Height |
Original image height |
X-Blur-Method |
Masking method used |
X-Det-Scale |
Detection scale factor |
X-Image-Id |
Image UUID (when storage enabled) |
X-Job-Id |
Job UUID (when storage enabled) |
Examples¶
curl (save masked image):
curl -X POST https://face-api.xylolabs.com/api/v1/mask \
-H "X-API-Key: xyl_your_api_key_here" \
-F "image=@photo.jpg" \
-F "method=pixelate" \
-F "strength=20" \
-F "format=png" \
-o masked_output.png
Python (download binary image):
import requests
response = requests.post(
"https://face-api.xylolabs.com/api/v1/mask",
headers={"X-API-Key": "xyl_your_api_key_here"},
files={"image": open("photo.jpg", "rb")},
params={"method": "gaussian", "strength": 99, "format": "jpeg", "quality": 90},
)
# Save the masked image
with open("masked.jpg", "wb") as f:
f.write(response.content)
# Read metadata from headers
face_count = response.headers.get("X-Face-Count")
total_ms = response.headers.get("X-Total-Ms")
print(f"Masked {face_count} face(s) in {total_ms}ms")
JavaScript (display in browser):
const formData = new FormData();
formData.append("image", fileInput.files[0]);
const response = await fetch(
"https://face-api.xylolabs.com/api/v1/mask?method=pixelate&strength=15",
{
method: "POST",
headers: { "X-API-Key": "xyl_your_api_key_here" },
body: formData,
}
);
const blob = await response.blob();
const url = URL.createObjectURL(blob);
document.getElementById("result").src = url;
console.log("Faces masked:", response.headers.get("X-Face-Count"));
Python (in-memory bytes to bytes):
import requests
from io import BytesIO
from PIL import Image
# Read image bytes from any source
image_bytes = download_from_somewhere()
response = requests.post(
"https://face-api.xylolabs.com/api/v1/mask",
headers={"X-API-Key": "xyl_your_api_key_here"},
files={"image": ("image.jpg", image_bytes, "image/jpeg")},
params={"method": "elliptical", "padding": 0.3},
)
# Use result directly in memory
masked_image = Image.open(BytesIO(response.content))
Masking Methods¶
| Method | Description |
|---|---|
gaussian |
Gaussian blur (default). strength controls kernel size. |
pixelate |
Pixelation effect. strength controls block size. |
solid |
Black rectangle over face region. |
elliptical |
Gaussian blur with elliptical mask for natural look. |
Admin Endpoints¶
All admin endpoints require HTTP Basic Authentication and are prefixed with /api/v1/admin.
Admin endpoints require FACE_API_STORAGE_ENABLED=true (returns 501 otherwise).
GET /api/v1/admin/stats¶
Aggregate statistics.
Response:
{
"total_images": 1250,
"total_jobs": 3420,
"total_faces_detected": 8910,
"avg_inference_ms": 14.52,
"storage_used_bytes": 524288000,
"jobs_by_type": { "detect": 2100, "mask": 1320 },
"jobs_by_method": { "gaussian": 800, "pixelate": 400, "solid": 120 }
}
GET /api/v1/admin/images¶
Paginated list of stored images.
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
page |
int | 1 | ≥ 1 | Page number |
per_page |
int | 20 | 1–100 | Items per page |
Response:
{
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"filename": "photo.jpg",
"content_type": "image/jpeg",
"size_bytes": 245760,
"width": 1920,
"height": 1080,
"original_url": "https://static.face-api.xylolabs.com/originals/550e.../photo.jpg",
"job_count": 3,
"created_at": "2026-04-06T10:30:00+00:00"
}
],
"total": 1250,
"page": 1,
"per_page": 10,
"pages": 125
}
GET /api/v1/admin/images/{image_id}¶
Image detail with associated jobs.
DELETE /api/v1/admin/images/{image_id}¶
Delete image, all associated jobs, and S3 objects. Returns 204 No Content.
GET /api/v1/admin/images/{image_id}/download¶
Redirects (302) to a presigned S3 URL for the original image.
GET /api/v1/admin/jobs¶
Paginated and filterable job list.
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
int | 1 | Page number |
per_page |
int | 20 | Items per page (1–100) |
job_type |
string | — | Filter: detect or mask |
method |
string | — | Filter by blur method |
curl -u admin:password "https://face-api.xylolabs.com/api/v1/admin/jobs?job_type=mask&method=gaussian"
GET /api/v1/admin/jobs/{job_id}¶
Job detail with full detection data.
GET /api/v1/admin/jobs/{job_id}/download¶
Redirects (302) to a presigned S3 URL for the processed image.
API Key Management¶
Admin endpoints for managing API keys. Requires HTTP Basic Auth and FACE_API_STORAGE_ENABLED=true.
POST /api/v1/admin/api-keys¶
Create a new API key.
Request Body (JSON):
Response (201 Created):
{
"id": "a1b2c3d4-e5f6-...",
"key": "xyl_7f3a8b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e",
"name": "Production App",
"is_active": true,
"request_count": 0,
"last_used_at": null,
"created_at": "2026-04-06T10:30:00+00:00"
}
The full key is stored in plaintext and always visible through the admin API.
Example¶
curl -u admin:password -X POST \
https://face-api.xylolabs.com/api/v1/admin/api-keys \
-H "Content-Type: application/json" \
-d '{"name": "Production App"}'
GET /api/v1/admin/api-keys¶
List all API keys with usage metrics.
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
int | 1 | Page number |
per_page |
int | 20 | Items per page (1–100) |
GET /api/v1/admin/api-keys/{key_id}¶
Get a specific API key with usage metrics.
PATCH /api/v1/admin/api-keys/{key_id}¶
Update key name or revoke/reactivate.
Request Body (JSON, all fields optional):
Revoke a key¶
curl -u admin:password -X PATCH \
https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-... \
-H "Content-Type: application/json" \
-d '{"is_active": false}'
DELETE /api/v1/admin/api-keys/{key_id}¶
Permanently delete an API key. Returns 204 No Content.
Error Codes¶
| Status | Description |
|---|---|
400 |
Bad request — invalid image format, image too small (< 10x10), decode failure |
401 |
Unauthorized — missing or invalid API key / admin credentials |
403 |
Forbidden — API key has been revoked |
404 |
Not found — image or job ID does not exist |
413 |
Payload too large — image exceeds size limit (default 20 MB) or pixel limit (default 30M pixels) |
429 |
Rate limit exceeded — retry after the duration in Retry-After header |
501 |
Storage not enabled — admin/key endpoints require FACE_API_STORAGE_ENABLED=true |
503 |
Server busy — max concurrent requests reached, retry after 1 second |
Error response format:
Image Constraints¶
| Constraint | Default | Config Variable |
|---|---|---|
| Max file size | 20 MB | FACE_API_MAX_IMAGE_BYTES |
| Max pixel count | 30,000,000 | FACE_API_MAX_IMAGE_PIXELS |
| Min dimensions | 10 x 10 px | — |
| Supported formats | JPEG, PNG, WebP, BMP, TIFF | — |
Concurrency¶
| Setting | Default | Config Variable |
|---|---|---|
| Max concurrent requests | 10 | FACE_API_MAX_CONCURRENT_REQUESTS |
| Max parallel inferences | 2 | FACE_API_MAX_CONCURRENT_INFERENCE |
When max concurrent requests is reached, the API returns 503 Server busy with Retry-After: 1.
Configuration Reference¶
All settings are configured via environment variables.
Core¶
| Variable | Type | Default | Description |
|---|---|---|---|
FACE_API_MODEL |
string | scrfd_10g |
SCRFD model variant |
FACE_API_MODEL_DIR |
string | models |
Directory containing .onnx model files |
FACE_API_DET_SIZE |
int | 640 |
Detection input size (square) |
FACE_API_CONFIDENCE |
float | 0.5 |
Default confidence threshold |
FACE_API_NMS_THRESHOLD |
float | 0.4 |
Default NMS threshold |
FACE_API_BLUR_METHOD |
string | gaussian |
Default masking method |
FACE_API_BLUR_STRENGTH |
int | 199 |
Default blur strength |
FACE_API_PADDING |
float | 0.15 |
Default bbox padding |
FACE_API_MAX_IMAGE_BYTES |
int | 20971520 |
Max upload size (bytes) |
FACE_API_MAX_IMAGE_PIXELS |
int | 30000000 |
Max image pixel count |
FACE_API_ORT_THREADS |
int | 0 |
ONNX Runtime threads (0 = auto) |
Concurrency¶
| Variable | Type | Default | Description |
|---|---|---|---|
FACE_API_MAX_CONCURRENT_INFERENCE |
int | 2 |
Max parallel inference calls |
FACE_API_MAX_CONCURRENT_REQUESTS |
int | 10 |
Max in-flight requests |
Authentication¶
| Variable | Type | Default | Description |
|---|---|---|---|
FACE_API_API_KEY_ENABLED |
bool | true |
Enable API key auth on public endpoints |
FACE_API_ADMIN_USER |
string | — | Admin username |
FACE_API_ADMIN_PASSWORD |
string | — | Admin password |
Storage (Optional)¶
| Variable | Type | Default | Description |
|---|---|---|---|
FACE_API_STORAGE_ENABLED |
bool | false |
Enable DB + S3 persistence |
FACE_API_DATABASE_URL |
string | postgresql+asyncpg://... |
PostgreSQL connection string |
FACE_API_S3_ENDPOINT |
string | http://localhost:9000 |
S3/MinIO endpoint |
FACE_API_S3_ACCESS_KEY |
string | — | S3 access key |
FACE_API_S3_SECRET_KEY |
string | — | S3 secret key |
FACE_API_S3_BUCKET |
string | face-api |
S3 bucket name |
FACE_API_S3_REGION |
string | us-east-1 |
S3 region |
FACE_API_S3_PUBLIC_ENDPOINT |
string | — | Public URL for stored objects |
Network¶
| Variable | Type | Default | Description |
|---|---|---|---|
FACE_API_CORS_ORIGINS |
string | — | Comma-separated allowed origins |
FACE_API_RATE_LIMIT_DETECT |
string | 300/minute |
Detect endpoint rate limit |
FACE_API_RATE_LIMIT_MASK |
string | 150/minute |
Mask endpoint rate limit |
Security Headers¶
All responses include:
| Header | Value |
|---|---|
X-Content-Type-Options |
nosniff |
X-Frame-Options |
DENY |
Referrer-Policy |
strict-origin-when-cross-origin |
Permissions-Policy |
camera=(), microphone=() |
Strict-Transport-Security |
max-age=63072000; includeSubDomains |
Response Schemas¶
All response schemas are defined as Pydantic models in app/models.py. See the interactive API docs at /docs or /redoc for full schema details.