Skip to content

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입니다.

X-API-Key: xyl_a1b2c3d4e5f6...

키는 관리자 콘솔이나 API 키 관리 엔드포인트에서 만들 수 있습니다. 키 없이 요청하면 401을 반환합니다.

관리자 세션 / HTTP Basic (관리자 엔드포인트)

관리자 인증 방식: - 브라우저 관리자 콘솔: /api/v1/admin/auth/login으로 만든 쿠키 기반 관리자 세션 - 직접 /api/v1/admin/* API를 호출하는 클라이언트: HTTP Basic 인증

Authorization: Basic base64(username:password)

로그인 실패는 IP별로 추적하며, 연속 실패 시 차단합니다.


요청 제한 (Rate Limiting)

엔드포인트 제한
POST /api/v1/detect 300회/분
POST /api/v1/mask 150회/분

IP별로 제한합니다. 초과하면 429 Too Many RequestsRetry-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
}

예제

curl https://face-api.xylolabs.com/health
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)
curl -u admin:password https://face-api.xylolabs.com/api/v1/admin/stats
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)
curl -u admin:password "https://face-api.xylolabs.com/api/v1/admin/images?page=1&per_page=10"
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.comFACE_API_S3_PUBLIC_ENDPOINT가 설정된 경우의 공개 처리 자산 호스트 예시입니다.

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

이미지 상세 정보와 관련 작업 목록을 반환합니다.

  • 요청 형식: 없음
  • 응답 형식: JSON (StoredImageDetailResponse)
curl -u admin:password https://face-api.xylolabs.com/api/v1/admin/images/550e8400-...
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)
curl -u admin:password -X DELETE https://face-api.xylolabs.com/api/v1/admin/images/550e8400-...
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)
curl -u admin:password https://face-api.xylolabs.com/api/v1/admin/jobs/550e8400-...
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):

{ "name": "운영 앱" }

응답 (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)
curl -u admin:password https://face-api.xylolabs.com/api/v1/admin/api-keys
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)
curl -u admin:password https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...
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)
{ "name": "새 이름", "is_active": false }

키 비활성화

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)
curl -u admin:password -X DELETE \
  https://face-api.xylolabs.com/api/v1/admin/api-keys/a1b2c3d4-...
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초 후 재시도

오류 응답 형식:

{ "detail": "오류 설명 문자열" }

이미지 제한

제한 항목
최대 파일 크기 20 MB
최대 픽셀 수 30,000,000
최소 크기 10 x 10 px
지원 포맷 JPEG, PNG, WebP, BMP, TIFF