Xylolabs Face API 레퍼런스¶
기본 URL: https://face-api.xylolabs.com
대화형 문서: /docs (Swagger UI) | /redoc (ReDoc)
인증¶
API 키 (공개 엔드포인트)¶
공개 엔드포인트는 FACE_API_API_KEY_ENABLED=true일 때 X-API-Key 헤더가 필요합니다. 기본값은 true입니다.
키는 관리자 콘솔이나 API 키 관리 엔드포인트에서 만들 수 있습니다. 키 없이 요청하면 401을 반환합니다.
관리자 세션 / HTTP Basic (관리자 엔드포인트)¶
관리자 인증 방식:
- 브라우저 관리자 콘솔: /api/v1/admin/auth/login으로 만든 쿠키 기반 관리자 세션
- 직접 /api/v1/admin/* API를 호출하는 클라이언트: HTTP Basic 인증
로그인 실패는 IP별로 추적하며, 연속 실패 시 차단합니다.
요청 제한 (Rate Limiting)¶
| 엔드포인트 | 제한 |
|---|---|
POST /api/v1/detect |
300회/분 |
POST /api/v1/mask |
150회/분 |
IP별로 제한합니다. 초과하면 429 Too Many Requests와 Retry-After 헤더를 반환합니다.
공개 엔드포인트¶
GET /health¶
서버 상태를 확인합니다. 인증은 필요하지 않습니다. 모델과 필요한 DB/스토리지 의존성이 준비된 경우에만 200을 반환하고, 그렇지 않으면 상태 정보를 담은 503을 반환합니다.
- 요청 형식: 없음
- 응답 형식: JSON (
HealthResponse)
응답:
{
"status": "ok",
"model": "scrfd_10g",
"model_loaded": true,
"input_size": [640, 640],
"supports_landmarks": true,
"database_enabled": false,
"database_ready": null,
"storage_enabled": false,
"storage_ready": null
}
예제¶
import requests
response = requests.get("https://face-api.xylolabs.com/health")
print(response.json())
const response = await fetch("https://face-api.xylolabs.com/health");
console.log(await response.json());
using var client = new HttpClient();
var response = await client.GetAsync("https://face-api.xylolabs.com/health");
Console.WriteLine(await response.Content.ReadAsStringAsync());
POST /api/v1/detect¶
이미지에서 얼굴을 감지하고, 바운딩 박스와 랜드마크, 신뢰도 점수가 담긴 JSON을 반환합니다.
Content-Type: multipart/form-data
- 요청 형식:
multipart/form-data - 응답 형식: JSON (
DetectResponse)
요청 파라미터:
| 파라미터 | 타입 | 기본값 | 범위 | 설명 |
|---|---|---|---|---|
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_name": "scrfd_10g"
}
}
예제¶
curl:
curl -X POST "https://face-api.xylolabs.com/api/v1/detect?confidence=0.6&max_faces=5" \
-H "X-API-Key: xyl_your_api_key_here" \
-F "image=@photo.jpg"
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}개의 얼굴 감지`);
C#:
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "xyl_your_api_key_here");
using var content = new MultipartFormDataContent();
content.Add(new ByteArrayContent(File.ReadAllBytes("photo.jpg")), "image", "photo.jpg");
var response = await client.PostAsync(
"https://face-api.xylolabs.com/api/v1/detect?confidence=0.6", content);
var json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
var faceCount = doc.RootElement.GetProperty("summary").GetProperty("face_count").GetInt32();
Console.WriteLine($"{faceCount}개 얼굴 감지");
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
- 요청 형식:
multipart/form-data - 응답 형식: 바이너리 이미지 (
image/jpeg,image/png,image/webp)
요청 파라미터:
| 파라미터 | 타입 | 기본값 | 범위 | 설명 |
|---|---|---|---|---|
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?method=pixelate&strength=20&format=png" \
-H "X-API-Key: xyl_your_api_key_here" \
-F "image=@photo.jpg" \
-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"));
C#:
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "xyl_your_api_key_here");
using var content = new MultipartFormDataContent();
content.Add(new ByteArrayContent(File.ReadAllBytes("photo.jpg")), "image", "photo.jpg");
var response = await client.PostAsync(
"https://face-api.xylolabs.com/api/v1/mask?method=pixelate&strength=20", content);
var imageBytes = await response.Content.ReadAsByteArrayAsync();
File.WriteAllBytes("masked.jpg", imageBytes);
var faceCount = response.Headers.GetValues("X-Face-Count").First();
Console.WriteLine($"{faceCount}개 얼굴 마스킹 완료");
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 |
타원형 마스크를 씌운 가우시안 블러입니다. 보다 자연스럽게 보입니다. |
관리자 엔드포인트¶
관리자 엔드포인트는 /api/v1/admin 경로를 사용합니다. 브라우저 관리자 콘솔은 쿠키 기반 관리자 세션을 사용하고, 직접 관리자 API를 호출하는 클라이언트는 HTTP Basic 인증도 사용할 수 있습니다.
GET /api/v1/admin/stats¶
전체 통계를 반환합니다. 스토리지 기반 모드가 필요하며, FACE_API_STORAGE_ENABLED=false이면 501을 반환합니다.
- 요청 형식: 없음
- 응답 형식: JSON (
StatsResponse)
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
"https://face-api.xylolabs.com/api/v1/admin/stats",
auth=HTTPBasicAuth("admin", "password"),
)
print(response.json())
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/stats", {
headers: { Authorization: `Basic ${basic}` },
});
console.log(await response.json());
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
var response = await client.GetAsync("https://face-api.xylolabs.com/api/v1/admin/stats");
Console.WriteLine(await response.Content.ReadAsStringAsync());
{
"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¶
저장된 이미지 목록을 반환합니다. 비공개 원본 다운로드 URL과, 있으면 가장 최근 처리 결과 URL도 함께 반환합니다. 스토리지 기반 모드가 필요하며, FACE_API_STORAGE_ENABLED=false이면 501을 반환합니다.
- 요청 형식: 없음(쿼리 문자열만 사용)
- 응답 형식: JSON (
PaginatedResponse[StoredImageResponse])
아직 처리 결과가 없으면 processed_url 값은 null입니다.
FACE_API_S3_PUBLIC_ENDPOINT가 설정되어 있으면 processed_url은 공개 처리 자산 호스트를 가리킬 수 있습니다.
| 파라미터 | 타입 | 기본값 | 설명 |
|---|---|---|---|
page |
int | 1 | 페이지 번호 |
per_page |
int | 20 | 페이지당 항목 수 (1-100) |
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
"https://face-api.xylolabs.com/api/v1/admin/images?page=1&per_page=10",
auth=HTTPBasicAuth("admin", "password"),
)
print(response.json())
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/images?page=1&per_page=10", {
headers: { Authorization: `Basic ${basic}` },
});
console.log(await response.json());
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
var response = await client.GetAsync("https://face-api.xylolabs.com/api/v1/admin/images?page=1&per_page=10");
Console.WriteLine(await response.Content.ReadAsStringAsync());
{
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"filename": "photo.jpg",
"content_type": "image/jpeg",
"size_bytes": 245760,
"width": 1920,
"height": 1080,
"original_url": "https://signed.example.com/originals/...",
"processed_url": "https://static.face-api.xylolabs.com/face-api/processed/...",
"job_count": 3,
"created_at": "2026-04-06T10:30:00+00:00"
}
],
"total": 1250, "page": 1, "per_page": 10, "pages": 125
}
signed.example.com은 비공개 서명 원본 다운로드 URL 예시를 보여 주기 위한 자리표시자입니다.
static.face-api.xylolabs.com은 FACE_API_S3_PUBLIC_ENDPOINT가 설정된 경우의 공개 처리 자산 호스트 예시입니다.
GET /api/v1/admin/images/{image_id}¶
이미지 상세 정보와 관련 작업 목록을 반환합니다.
- 요청 형식: 없음
- 응답 형식: JSON (
StoredImageDetailResponse)
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
"https://face-api.xylolabs.com/api/v1/admin/images/550e8400-...",
auth=HTTPBasicAuth("admin", "password"),
)
print(response.json())
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/images/550e8400-...", {
headers: { Authorization: `Basic ${basic}` },
});
console.log(await response.json());
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
var response = await client.GetAsync("https://face-api.xylolabs.com/api/v1/admin/images/550e8400-...");
Console.WriteLine(await response.Content.ReadAsStringAsync());
DELETE /api/v1/admin/images/{image_id}¶
이미지와 관련 작업, S3 객체를 모두 삭제합니다. 성공 시 204를 반환합니다.
- 요청 형식: 없음
- 응답 형식: 빈 바디 (
204 No Content)
import requests
from requests.auth import HTTPBasicAuth
response = requests.delete(
"https://face-api.xylolabs.com/api/v1/admin/images/550e8400-...",
auth=HTTPBasicAuth("admin", "password"),
)
print(response.status_code)
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/images/550e8400-...", {
method: "DELETE",
headers: { Authorization: `Basic ${basic}` },
});
console.log(response.status);
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
var response = await client.DeleteAsync("https://face-api.xylolabs.com/api/v1/admin/images/550e8400-...");
Console.WriteLine(response.StatusCode);
GET /api/v1/admin/images/{image_id}/download¶
원본 이미지 다운로드 URL로 리다이렉트(302)합니다.
- 요청 형식: 없음
- 응답 형식: 리다이렉트 (
302 Found)
curl -u admin:password -L https://face-api.xylolabs.com/api/v1/admin/images/550e8400-.../download -o original.jpg
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
"https://face-api.xylolabs.com/api/v1/admin/images/550e8400-.../download",
auth=HTTPBasicAuth("admin", "password"),
allow_redirects=True,
)
with open("original.jpg", "wb") as f:
f.write(response.content)
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/images/550e8400-.../download", {
headers: { Authorization: `Basic ${basic}` },
});
const blob = await response.blob();
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
var response = await client.GetAsync("https://face-api.xylolabs.com/api/v1/admin/images/550e8400-.../download");
await using var output = File.Create("original.jpg");
await response.Content.CopyToAsync(output);
GET /api/v1/admin/jobs¶
작업 목록을 반환합니다(페이지네이션, 필터 지원). 스토리지 기반 모드가 필요하며, FACE_API_STORAGE_ENABLED=false이면 501을 반환합니다.
- 요청 형식: 없음(쿼리 문자열만 사용)
- 응답 형식: JSON (
PaginatedResponse[JobResponse])
| 파라미터 | 타입 | 기본값 | 설명 |
|---|---|---|---|
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"
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
"https://face-api.xylolabs.com/api/v1/admin/jobs?job_type=mask&method=gaussian",
auth=HTTPBasicAuth("admin", "password"),
)
print(response.json())
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/jobs?job_type=mask&method=gaussian", {
headers: { Authorization: `Basic ${basic}` },
});
console.log(await response.json());
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
var response = await client.GetAsync("https://face-api.xylolabs.com/api/v1/admin/jobs?job_type=mask&method=gaussian");
Console.WriteLine(await response.Content.ReadAsStringAsync());
GET /api/v1/admin/jobs/{job_id}¶
작업 상세 정보를 반환합니다(감지 데이터 포함).
- 요청 형식: 없음
- 응답 형식: JSON (
JobDetailResponse)
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
"https://face-api.xylolabs.com/api/v1/admin/jobs/550e8400-...",
auth=HTTPBasicAuth("admin", "password"),
)
print(response.json())
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/jobs/550e8400-...", {
headers: { Authorization: `Basic ${basic}` },
});
console.log(await response.json());
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
var response = await client.GetAsync("https://face-api.xylolabs.com/api/v1/admin/jobs/550e8400-...");
Console.WriteLine(await response.Content.ReadAsStringAsync());
GET /api/v1/admin/jobs/{job_id}/download¶
처리된 이미지 URL로 리다이렉트(302)합니다.
- 요청 형식: 없음
- 응답 형식: 리다이렉트 (
302 Found)
curl -u admin:password -L https://face-api.xylolabs.com/api/v1/admin/jobs/550e8400-.../download -o processed.jpg
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
"https://face-api.xylolabs.com/api/v1/admin/jobs/550e8400-.../download",
auth=HTTPBasicAuth("admin", "password"),
allow_redirects=True,
)
with open("processed.jpg", "wb") as f:
f.write(response.content)
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/jobs/550e8400-.../download", {
headers: { Authorization: `Basic ${basic}` },
});
const blob = await response.blob();
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
var response = await client.GetAsync("https://face-api.xylolabs.com/api/v1/admin/jobs/550e8400-.../download");
await using var output = File.Create("processed.jpg");
await response.Content.CopyToAsync(output);
API 키 관리¶
API 키 관리를 위한 관리자 엔드포인트입니다. 브라우저 관리자 콘솔은 쿠키 기반 관리자 세션을 사용하고, 직접 관리자 API를 호출하는 클라이언트는 HTTP Basic 인증도 사용할 수 있습니다. 모든 /api/v1/admin/api-keys* 엔드포인트는 데이터베이스 기반 모드가 필요하며, DB 계층이 비활성화되어 있으면 501을 반환합니다.
POST /api/v1/admin/api-keys¶
새 API 키를 생성합니다. 데이터베이스 기반 모드가 필요하며, DB 계층이 비활성화되어 있으면 501을 반환합니다.
요청 본문 (JSON):
응답 (201):
{
"id": "a1b2c3d4-e5f6-...",
"key": "xyl_7f3a8b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e",
"name": "운영 앱",
"key_prefix": "xyl_7f3a8b2",
"key_suffix": "9b0c1d2e",
"key_preview": "xyl_7f3a8b2...9b0c1d2e",
"is_active": true,
"request_count": 0,
"last_used_at": null,
"created_at": "2026-04-06T10:30:00+00:00"
}
- 요청 형식: JSON
- 응답 형식: JSON (
ApiKeyCreatedResponse)
예제¶
curl -u admin:password -X POST \
https://face-api.xylolabs.com/api/v1/admin/api-keys \
-H "Content-Type: application/json" \
-d '{"name": "운영 앱"}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.post(
"https://face-api.xylolabs.com/api/v1/admin/api-keys",
auth=HTTPBasicAuth("admin", "password"),
json={"name": "운영 앱"},
)
print(response.json())
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/api-keys", {
method: "POST",
headers: {
Authorization: `Basic ${basic}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ name: "운영 앱" }),
});
console.log(await response.json());
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
using var content = new StringContent("{\"name\":\"운영 앱\"}", Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://face-api.xylolabs.com/api/v1/admin/api-keys", content);
Console.WriteLine(await response.Content.ReadAsStringAsync());
GET /api/v1/admin/api-keys¶
API 키 전체 목록과 사용량을 조회합니다. 전체 원본 키는 반환하지 않고 메타데이터만 돌려줍니다. 데이터베이스 기반 모드가 필요하며, DB 계층이 비활성화되어 있으면 501을 반환합니다.
- 요청 형식: 없음(쿼리 문자열만 사용)
- 응답 형식: JSON (
PaginatedResponse[ApiKeyResponse])
| 파라미터 | 타입 | 기본값 | 설명 |
|---|---|---|---|
page |
int | 1 | 페이지 번호 |
per_page |
int | 20 | 페이지당 항목 수 (1-100) |
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
"https://face-api.xylolabs.com/api/v1/admin/api-keys",
auth=HTTPBasicAuth("admin", "password"),
)
print(response.json())
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/api-keys", {
headers: { Authorization: `Basic ${basic}` },
});
console.log(await response.json());
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
var response = await client.GetAsync("https://face-api.xylolabs.com/api/v1/admin/api-keys");
Console.WriteLine(await response.Content.ReadAsStringAsync());
{
"items": [
{
"id": "a1b2c3d4-e5f6-...",
"name": "운영 앱",
"key_prefix": "xyl_7f3a8b2",
"key_suffix": "9b0c1d2e",
"key_preview": "xyl_7f3a8b2...9b0c1d2e",
"is_active": true,
"request_count": 142,
"last_used_at": "2026-04-12T10:30:00+00:00",
"created_at": "2026-04-06T10:30:00+00:00"
}
],
"total": 1,
"page": 1,
"per_page": 20,
"pages": 1
}
GET /api/v1/admin/api-keys/{key_id}¶
특정 API 키의 상세 정보와 사용량을 조회합니다. 전체 원본 키는 반환하지 않고 메타데이터만 돌려줍니다. 데이터베이스 기반 모드가 필요하며, DB 계층이 비활성화되어 있으면 501을 반환합니다.
- 요청 형식: 없음
- 응답 형식: JSON (
ApiKeyResponse)
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
"https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...",
auth=HTTPBasicAuth("admin", "password"),
)
print(response.json())
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...", {
headers: { Authorization: `Basic ${basic}` },
});
console.log(await response.json());
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
var response = await client.GetAsync("https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...");
Console.WriteLine(await response.Content.ReadAsStringAsync());
{
"id": "a1b2c3d4-e5f6-...",
"name": "운영 앱",
"key_prefix": "xyl_7f3a8b2",
"key_suffix": "9b0c1d2e",
"key_preview": "xyl_7f3a8b2...9b0c1d2e",
"is_active": true,
"request_count": 142,
"last_used_at": "2026-04-12T10:30:00+00:00",
"created_at": "2026-04-06T10:30:00+00:00"
}
PATCH /api/v1/admin/api-keys/{key_id}¶
키 이름을 변경하거나 비활성화/재활성화합니다. 데이터베이스 기반 모드가 필요하며, DB 계층이 비활성화되어 있으면 501을 반환합니다.
- 요청 형식: JSON
- 응답 형식: JSON (
ApiKeyResponse)
키 비활성화¶
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}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.patch(
"https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...",
auth=HTTPBasicAuth("admin", "password"),
json={"is_active": False},
)
print(response.json())
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...", {
method: "PATCH",
headers: {
Authorization: `Basic ${basic}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ is_active: false }),
});
console.log(await response.json());
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
using var request = new HttpRequestMessage(new HttpMethod("PATCH"), "https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...")
{
Content = new StringContent("{\"is_active\":false}", Encoding.UTF8, "application/json"),
};
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());
DELETE /api/v1/admin/api-keys/{key_id}¶
API 키를 영구 삭제합니다. 데이터베이스 기반 모드가 필요하며, DB 계층이 비활성화되어 있으면 501을 반환합니다. 성공 시 204를 반환합니다.
- 요청 형식: 없음
- 응답 형식: 빈 바디 (
204 No Content)
import requests
from requests.auth import HTTPBasicAuth
response = requests.delete(
"https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...",
auth=HTTPBasicAuth("admin", "password"),
)
print(response.status_code)
const basic = btoa("admin:password");
const response = await fetch("https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...", {
method: "DELETE",
headers: { Authorization: `Basic ${basic}` },
});
console.log(response.status);
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes("admin:password"))
);
var response = await client.DeleteAsync("https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...");
Console.WriteLine(response.StatusCode);
오류 코드¶
| 상태 코드 | 설명 |
|---|---|
400 |
잘못된 요청: 지원 안 하는 이미지 포맷, 10x10 미만, 디코딩 실패 |
401 |
인증 실패: API 키나 관리자 인증 정보가 없거나 틀림 |
403 |
접근 거부: API 키가 비활성 상태 |
404 |
없음: 해당 이미지나 작업 ID가 없음 |
413 |
크기 초과: 이미지가 20 MB 또는 3천만 픽셀을 넘음 |
429 |
요청 제한 초과: Retry-After 헤더 시간만큼 기다린 뒤 재시도 |
503 |
서버 바쁨: 1초 후 재시도 |
오류 응답 형식:
이미지 제한¶
| 제한 항목 | 값 |
|---|---|
| 최대 파일 크기 | 20 MB |
| 최대 픽셀 수 | 30,000,000 |
| 최소 크기 | 10 x 10 px |
| 지원 포맷 | JPEG, PNG, WebP, BMP, TIFF |