Skip to content

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.

X-API-Key: xyl_a1b2c3d4e5f6...

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.

Authorization: Basic base64(username:password)

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

curl https://face-api.xylolabs.com/health

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.

curl -u admin:password https://face-api.xylolabs.com/api/v1/admin/stats

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
curl -u admin:password "https://face-api.xylolabs.com/api/v1/admin/images?page=1&per_page=10"

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.

curl -u admin:password https://face-api.xylolabs.com/api/v1/admin/images/550e8400-...

DELETE /api/v1/admin/images/{image_id}

Delete image, all associated jobs, and S3 objects. Returns 204 No Content.

curl -u admin:password -X DELETE https://face-api.xylolabs.com/api/v1/admin/images/550e8400-...

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):

{ "name": "Production App" }

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)
curl -u admin:password https://face-api.xylolabs.com/api/v1/admin/api-keys

GET /api/v1/admin/api-keys/{key_id}

Get a specific API key with usage metrics.

curl -u admin:password https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...

PATCH /api/v1/admin/api-keys/{key_id}

Update key name or revoke/reactivate.

Request Body (JSON, all fields optional):

{ "name": "New Name", "is_active": false }

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.

curl -u admin:password -X DELETE \
  https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...

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:

{ "detail": "Human-readable error message" }

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.