Xylolabs Face API 레퍼런스¶
기본 URL: https://face-api.xylolabs.com
대화형 문서: /docs (Swagger UI) | /redoc (ReDoc)
인증¶
API 키 (공개 엔드포인트)¶
FACE_API_API_KEY_ENABLED=true로 설정하면 공개 엔드포인트(/api/v1/detect, /api/v1/mask)에 X-API-Key 헤더가 필요하다.
API 키는 관리자 API에서 관리한다. 키마다 요청 수와 최근 사용 시각을 추적한다.
API 키 인증을 끄면(기본값) 인증 없이 공개 엔드포인트를 쓸 수 있다.
HTTP Basic Auth (관리자 엔드포인트)¶
모든 /api/v1/admin/* 엔드포인트는 HTTP Basic 인증이 필요하다.
인증 정보는 FACE_API_ADMIN_USER와 FACE_API_ADMIN_PASSWORD 환경 변수로 설정한다. 로그인 실패는 IP별로 추적하고, 10회 연속 실패 시 차단한다.
요청 제한 (Rate Limiting)¶
| 엔드포인트 | 기본 제한 | 설정 변수 |
|---|---|---|
POST /api/v1/detect |
60회/분 | FACE_API_RATE_LIMIT_DETECT |
POST /api/v1/mask |
30회/분 | FACE_API_RATE_LIMIT_MASK |
IP별로 제한한다. 초과하면 429 Too Many Requests와 Retry-After 헤더를 반환한다.
공개 엔드포인트¶
GET /health¶
서버 상태 확인. 인증 불필요.
응답:
{
"status": "ok",
"model": "scrfd_10g",
"model_loaded": true,
"input_size": [640, 640],
"supports_landmarks": true,
"storage_enabled": false
}
예제¶
POST /api/v1/detect¶
이미지에서 얼굴을 감지한다. 바운딩 박스, 랜드마크, 신뢰도 점수가 담긴 JSON을 반환한다.
Content-Type: multipart/form-data
요청 파라미터:
| 파라미터 | 타입 | 기본값 | 범위 | 설명 |
|---|---|---|---|---|
image |
file | 필수 | — | 이미지 파일 (JPEG, PNG, WebP, BMP, TIFF) |
confidence |
float | 0.5 | 0.01–1.0 | 최소 감지 신뢰도 |
nms_threshold |
float | 0.4 | 0.01–1.0 | NMS(Non-Maximum Suppression) IoU 임계값 |
max_faces |
int | 0 | ≥ 0 | 반환할 최대 얼굴 수 (0 = 무제한) |
응답 (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
}
}
예제¶
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"{data['summary']['face_count']}개의 얼굴 감지")
for face in data["detections"]:
bbox = face["bbox"]
print(f" 얼굴 #{face['index']}: 신뢰도={face['confidence']:.4f}, "
f"영역=({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(`${data.summary.face_count}개의 얼굴 감지`);
Python (바이너리 바이트 업로드):
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¶
이미지에서 얼굴을 감지하고 마스킹(블러/픽셀화)한다. 처리된 이미지를 바이너리로 반환한다.
Content-Type: multipart/form-data
요청 파라미터:
| 파라미터 | 타입 | 기본값 | 범위 | 설명 |
|---|---|---|---|---|
image |
file | 필수 | — | 이미지 파일 (JPEG, PNG, WebP, BMP, TIFF) |
confidence |
float | 0.5 | 0.01–1.0 | 최소 감지 신뢰도 |
nms_threshold |
float | 0.4 | 0.01–1.0 | NMS IoU 임계값 |
max_faces |
int | 0 | ≥ 0 | 마스킹할 최대 얼굴 수 (0 = 전체) |
method |
string | gaussian |
gaussian, pixelate, solid, elliptical |
마스킹 방식 |
strength |
int | 199 | 1–999 | 블러 커널 크기 또는 픽셀화 블록 크기 |
padding |
float | 0.15 | 0.0–2.0 | 마스킹 전 바운딩 박스 확장 비율 |
format |
string | jpeg |
jpeg, png, webp |
출력 이미지 포맷 |
quality |
int | 95 | 1–100 | 출력 압축 품질 |
응답: 메타데이터 헤더가 붙은 원시 이미지 바이트:
| 헤더 | 설명 |
|---|---|
X-Request-Id |
요청 UUID |
X-Face-Count |
마스킹된 얼굴 수 |
X-Decode-Ms |
이미지 디코딩 시간 (ms) |
X-Inference-Ms |
모델 추론 시간 (ms) |
X-Mask-Ms |
마스킹 시간 (ms) |
X-Encode-Ms |
출력 인코딩 시간 (ms) |
X-Total-Ms |
총 처리 시간 (ms) |
X-Model |
모델명 |
X-Image-Width |
원본 이미지 너비 |
X-Image-Height |
원본 이미지 높이 |
X-Blur-Method |
사용된 마스킹 방식 |
X-Det-Scale |
감지 스케일 팩터 |
X-Image-Id |
이미지 UUID (스토리지 활성화 시) |
X-Job-Id |
작업 UUID (스토리지 활성화 시) |
예제¶
curl (마스킹된 이미지 저장):
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 (바이너리 이미지 다운로드):
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},
)
# 마스킹된 이미지 저장
with open("masked.jpg", "wb") as f:
f.write(response.content)
# 헤더에서 메타데이터 읽기
face_count = response.headers.get("X-Face-Count")
total_ms = response.headers.get("X-Total-Ms")
print(f"{face_count}개 얼굴을 {total_ms}ms에 마스킹 완료")
JavaScript (브라우저에서 표시):
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("마스킹된 얼굴 수:", response.headers.get("X-Face-Count"));
Python (메모리 내 바이트 변환):
import requests
from io import BytesIO
from PIL import Image
# 외부 소스에서 이미지 바이트 읽기
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},
)
# 결과를 메모리에서 직접 사용
masked_image = Image.open(BytesIO(response.content))
마스킹 방식¶
| 방식 | 설명 |
|---|---|
gaussian |
가우시안 블러 (기본값). strength로 커널 크기를 정한다. |
pixelate |
픽셀화 효과. strength로 블록 크기를 정한다. |
solid |
얼굴 영역을 검은색 사각형으로 덮는다. |
elliptical |
타원형 마스크를 씌운 가우시안 블러. 자연스럽다. |
관리자 엔드포인트¶
모든 관리자 엔드포인트는 HTTP Basic 인증이 필요하고 /api/v1/admin 경로를 쓴다.
FACE_API_STORAGE_ENABLED=true가 필요하다. 꺼져 있으면 501을 반환한다.
GET /api/v1/admin/stats¶
전체 통계 조회.
응답:
{
"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¶
저장된 이미지 목록 (페이지네이션).
| 파라미터 | 타입 | 기본값 | 범위 | 설명 |
|---|---|---|---|---|
page |
int | 1 | ≥ 1 | 페이지 번호 |
per_page |
int | 20 | 1–100 | 페이지당 항목 수 |
응답:
{
"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}¶
이미지 상세 정보와 관련 작업 목록.
DELETE /api/v1/admin/images/{image_id}¶
이미지와 관련 작업, S3 객체를 모두 삭제한다. 204 No Content 반환.
GET /api/v1/admin/images/{image_id}/download¶
원본 이미지의 presigned S3 URL로 리다이렉트(302)한다.
GET /api/v1/admin/jobs¶
작업 목록 (페이지네이션, 필터 지원).
| 파라미터 | 타입 | 기본값 | 설명 |
|---|---|---|---|
page |
int | 1 | 페이지 번호 |
per_page |
int | 20 | 페이지당 항목 수 (1–100) |
job_type |
string | — | 필터: detect 또는 mask |
method |
string | — | 블러 방식으로 필터 |
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}¶
작업 상세 정보 (감지 데이터 포함).
GET /api/v1/admin/jobs/{job_id}/download¶
처리된 이미지의 presigned S3 URL로 리다이렉트(302)한다.
API 키 관리¶
API 키 관리를 위한 관리자 엔드포인트다. HTTP Basic 인증과 FACE_API_STORAGE_ENABLED=true가 필요하다.
POST /api/v1/admin/api-keys¶
새 API 키 생성.
요청 본문 (JSON):
응답 (201 Created):
{
"id": "a1b2c3d4-e5f6-...",
"key": "xyl_7f3a8b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e",
"name": "운영 앱",
"is_active": true,
"request_count": 0,
"last_used_at": null,
"created_at": "2026-04-06T10:30:00+00:00"
}
전체 키는 평문으로 저장하며, 관리자 API에서 언제든 확인할 수 있다.
예제¶
curl -u admin:password -X POST \
https://face-api.xylolabs.com/api/v1/admin/api-keys \
-H "Content-Type: application/json" \
-d '{"name": "운영 앱"}'
GET /api/v1/admin/api-keys¶
API 키 전체 목록과 사용량 조회.
| 파라미터 | 타입 | 기본값 | 설명 |
|---|---|---|---|
page |
int | 1 | 페이지 번호 |
per_page |
int | 20 | 페이지당 항목 수 (1–100) |
GET /api/v1/admin/api-keys/{key_id}¶
특정 API 키 상세 정보와 사용량 조회.
PATCH /api/v1/admin/api-keys/{key_id}¶
키 이름 변경 또는 비활성화/재활성화.
요청 본문 (JSON, 모든 필드 선택):
키 비활성화¶
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}¶
API 키를 영구 삭제한다. 204 No Content 반환.
오류 코드¶
| 상태 코드 | 설명 |
|---|---|
400 |
잘못된 요청: 지원 안 하는 이미지 포맷, 10x10 미만, 디코딩 실패 |
401 |
인증 실패: API 키나 관리자 인증 정보가 없거나 틀림 |
403 |
접근 거부: API 키가 비활성 상태 |
404 |
없음: 해당 이미지나 작업 ID가 없음 |
413 |
크기 초과: 이미지가 20 MB 또는 3천만 픽셀을 넘음 |
429 |
요청 제한 초과: Retry-After 헤더 시간만큼 기다린 뒤 재시도 |
501 |
스토리지 꺼짐: FACE_API_STORAGE_ENABLED=true 필요 |
503 |
서버 바쁨: 동시 요청 수 초과, 1초 후 재시도 |
오류 응답 형식:
이미지 제한¶
| 제한 항목 | 기본값 | 설정 변수 |
|---|---|---|
| 최대 파일 크기 | 20 MB | FACE_API_MAX_IMAGE_BYTES |
| 최대 픽셀 수 | 30,000,000 | FACE_API_MAX_IMAGE_PIXELS |
| 최소 크기 | 10 x 10 px | — |
| 지원 포맷 | JPEG, PNG, WebP, BMP, TIFF | — |
동시성¶
| 설정 | 기본값 | 설정 변수 |
|---|---|---|
| 최대 동시 요청 수 | 10 | FACE_API_MAX_CONCURRENT_REQUESTS |
| 최대 병렬 추론 수 | 2 | FACE_API_MAX_CONCURRENT_INFERENCE |
최대 동시 요청 수에 도달하면 503 Server busy와 Retry-After: 1을 반환한다.
설정 레퍼런스¶
모든 설정은 환경 변수로 구성한다.
핵심 설정¶
| 변수 | 타입 | 기본값 | 설명 |
|---|---|---|---|
FACE_API_MODEL |
string | scrfd_10g |
SCRFD 모델 변형 |
FACE_API_MODEL_DIR |
string | models |
.onnx 모델 파일 디렉터리 |
FACE_API_DET_SIZE |
int | 640 |
감지 입력 크기 (정사각형) |
FACE_API_CONFIDENCE |
float | 0.5 |
기본 신뢰도 임계값 |
FACE_API_NMS_THRESHOLD |
float | 0.4 |
기본 NMS 임계값 |
FACE_API_BLUR_METHOD |
string | gaussian |
기본 마스킹 방식 |
FACE_API_BLUR_STRENGTH |
int | 199 |
기본 블러 강도 |
FACE_API_PADDING |
float | 0.15 |
기본 바운딩 박스 패딩 |
FACE_API_MAX_IMAGE_BYTES |
int | 20971520 |
최대 업로드 크기 (바이트) |
FACE_API_MAX_IMAGE_PIXELS |
int | 30000000 |
최대 이미지 픽셀 수 |
FACE_API_ORT_THREADS |
int | 0 |
ONNX Runtime 스레드 수 (0 = 자동) |
동시성¶
| 변수 | 타입 | 기본값 | 설명 |
|---|---|---|---|
FACE_API_MAX_CONCURRENT_INFERENCE |
int | 2 |
최대 병렬 추론 호출 수 |
FACE_API_MAX_CONCURRENT_REQUESTS |
int | 10 |
최대 동시 요청 수 |
인증¶
| 변수 | 타입 | 기본값 | 설명 |
|---|---|---|---|
FACE_API_API_KEY_ENABLED |
bool | false |
공개 엔드포인트에 API 키 인증 활성화 |
FACE_API_ADMIN_USER |
string | — | 관리자 사용자명 |
FACE_API_ADMIN_PASSWORD |
string | — | 관리자 비밀번호 |
스토리지 (선택)¶
| 변수 | 타입 | 기본값 | 설명 |
|---|---|---|---|
FACE_API_STORAGE_ENABLED |
bool | false |
DB + S3 저장 활성화 |
FACE_API_DATABASE_URL |
string | postgresql+asyncpg://... |
PostgreSQL 연결 문자열 |
FACE_API_S3_ENDPOINT |
string | http://localhost:9000 |
S3/MinIO 엔드포인트 |
FACE_API_S3_ACCESS_KEY |
string | — | S3 접근 키 |
FACE_API_S3_SECRET_KEY |
string | — | S3 비밀 키 |
FACE_API_S3_BUCKET |
string | face-api |
S3 버킷 이름 |
FACE_API_S3_REGION |
string | us-east-1 |
S3 리전 |
FACE_API_S3_PUBLIC_ENDPOINT |
string | — | 저장된 객체의 퍼블릭 URL |
네트워크¶
| 변수 | 타입 | 기본값 | 설명 |
|---|---|---|---|
FACE_API_CORS_ORIGINS |
string | — | 허용된 출처 (쉼표 구분) |
FACE_API_RATE_LIMIT_DETECT |
string | 60/minute |
감지 엔드포인트 요청 제한 |
FACE_API_RATE_LIMIT_MASK |
string | 30/minute |
마스킹 엔드포인트 요청 제한 |
보안 헤더¶
모든 응답에 붙는 보안 헤더:
| 헤더 | 값 |
|---|---|
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 |
응답 스키마¶
모든 응답 스키마는 app/models.py에 Pydantic 모델로 정의되어 있다. 전체 스키마는 /docs 또는 /redoc에서 확인할 수 있다.