API 문서
별도 SDK 없이 표준 HTTP 한 번 호출로 기존 앱·키오스크·출입장치에 얼굴인증을 붙입니다. 모든 요청은 multipart/form-data로 이미지를 전송하며, 회사코드 헤더 한 줄로 회사별 데이터가 완전히 분리됩니다.
https://facematch.team1985.commultipart/form-data인증 · 회사코드
모든 엔드포인트는 발급받은 회사코드(License Key)를 요구합니다. X-License-Key 헤더로 전달하는 것을 권장하며, 폼 필드 license_key로도 받습니다.
X-License-Key: <회사코드>
에러 응답
실패 시 status 필드가 "error" 또는 "warning"으로 내려갑니다. 회사코드 관련 오류는 HTTP 상태코드로도 구분됩니다.
| 코드 | status | 의미 |
|---|---|---|
| 200 | success | 정상 처리 (인식 성공 여부는 recognized 필드로 구분) |
| 200 | warning | 얼굴 미감지·전경 얼굴 없음 등 — 요청은 정상이나 처리할 얼굴이 없음 |
| 400 | error | 이미지를 읽을 수 없음 (손상·미지원 형식) |
| 401 | error | 회사코드(license_key) 누락 |
| 403 | error | 유효하지 않은 회사코드 · 라이선스 유효기간 만료 |
식별키 · 부가정보
FaceMatch는 평문 개인정보를 보관하지 않는 PII-free 구조입니다. 회원은 파트너가 정한 키로 식별하고, 이름·연락처 등은 암호화된 부가정보로만 저장됩니다.
| 개념 | 설명 |
|---|---|
| unique_key | 파트너 시스템의 회원 식별자(실질 유일키). 회원번호·ID 등 아무 값이나 사용. 매칭 성공 시 이 값이 그대로 반환되므로 자체 DB 조회 키로 씁니다. |
| group_key | 회사 하위의 그룹 구분값(지점·반 등, 선택). 등록 시 지정하면 탐지 시 group_key로 해당 그룹 소속만 후보로 좁힐 수 있습니다. 재등록(갱신) 판정 범위 = (회사, group_key, unique_key). |
| extra | 자유 형식 부가정보 JSON(이름·연락처 등). AES-256-GCM 으로 암호화 저장되고 매칭 성공 시 복호화되어 그대로 반환됩니다 — 자체 서버가 없는 고객사도 매칭 결과만으로 회원정보를 회수할 수 있습니다. |
POST 얼굴등록 · 멀티임베딩
얼굴 이미지 1~N장에서 512차원 특징을 추출해 저장합니다. 정면·좌우·상하 등 여러 방향을 함께 등록하면 인식률이 크게 올라갑니다(권장 5장: 정면+네 모서리). 같은 (회사, group_key, unique_key)가 이미 있으면 갱신(update)됩니다.
요청 파라미터 · multipart/form-data
| 필드 | 타입 | 설명 |
|---|---|---|
| files 필수 | file[] | 얼굴 이미지 1~N장 (jpg/png). 같은 이름 files 필드를 반복 전송 |
| unique_key 필수 | string | 파트너 회원 식별키. 중복 시 기존 등록을 갱신 |
| poses 선택 | string | 이미지 순서와 1:1인 콤마구분 방향 라벨. 예: frontal,left_up,right_up,left_down,right_down |
| group_key 선택 | string | 그룹 구분값(지점·반 등). 탐지 시 같은 값으로 후보를 좁힐 수 있음 |
| extra 선택 | JSON string | 부가정보 JSON 객체. 암호화 저장 후 매칭 시 복호화 반환. 예: {"name":"홍길동","grade":"A"} |
| license_key 선택 | string | 회사코드 (헤더 X-License-Key 미사용 시) |
curl -X POST https://facematch.team1985.com/api/register \
-H "X-License-Key: <회사코드>" \
-F files=@front.jpg -F files=@lu.jpg -F files=@ru.jpg \
-F files=@ld.jpg -F files=@rd.jpg \
-F poses=frontal,left_up,right_up,left_down,right_down \
-F unique_key=member-1024 \
-F group_key=A \
-F extra='{"name":"홍길동","mobile":"01012345678"}'
{
"status": "success",
"action": "inserted", // 또는 "updated"
"face_id": 12,
"unique_key": "member-1024",
"group_key": "A",
"received": 5, // 받은 이미지 수
"accepted": 5, // 품질게이트 통과·저장 수
"poses": ["frontal","left_up","right_up","left_down","right_down"],
"face": {
"bbox": [142,88,320,300],
"det_score": 0.99
}
}
{ "status": "warning", "message": "얼굴을 감지하지 못했습니다." } 가 반환됩니다. 일부만 실패하면 통과한 장수만 accepted에 반영됩니다.POST 얼굴탐지 · 출석체크 1:N
이미지 1장에서 가장 가까운 전경 얼굴 1명만 골라 등록자와 1:N 매칭합니다. 여러 명이 줄 서 있어도 본인 1명만 인식해 출입·출석에 적합합니다.
요청 파라미터 · multipart/form-data
| 필드 | 타입 | 설명 |
|---|---|---|
| file 필수 | file | 인식할 이미지 |
| group_key 선택 | string | 지정 시 해당 그룹에 등록된 얼굴만 후보로 매칭 |
| threshold 선택 | float | 매칭 임계값. 미지정 시 회사 설정 → 전역 기본(0.35) 순으로 적용 |
| margin 선택 | float | 1·2위 유사도 차이 최소값(오인식 방지). 미지정 시 회사 설정 → 전역 기본(0.05) |
| min_face_ratio 선택 | float | 전경 얼굴로 인정할 최소 크기 비율 (기본 0.10) |
| license_key 선택 | string | 회사코드 (헤더 미사용 시) |
curl -X POST https://facematch.team1985.com/api/detect \
-H "X-License-Key: <회사코드>" \
-F file=@photo.jpg \
-F group_key=A
{
"status": "success",
"recognized": true,
"similarity": 0.987, // 1위 유사도
"similarity2": 0.141, // 2위 유사도
"gap": 0.846, // 1-2위 차이
"ambiguous": false, // 닮은 사람과 구분 애매 여부
"verified_by": null, // 2차 모델(buffalo_l)로 확정 시 모델명, 아니면 null
"threshold": 0.35,
"margin": 0.05,
"elapsed_ms": 176,
"person": {
"id": 12,
"unique_key": "member-1024",
"group_key": "A",
"extra": { "name": "홍길동", "mobile": "01012345678" }
},
"num_faces_in_frame": 1
}
recognized: false여도 status는 success입니다 — 인식 여부는 항상 recognized 필드로 판단하세요. 1·2위 유사도 차이가 margin보다 작으면 ambiguous: true와 함께 인식이 보류됩니다(재촬영 유도 권장). 응답에는 선택된 얼굴 좌표(selected)와 프레임 내 전체 얼굴(all_faces)도 포함됩니다.멀티모델 2차검증 & 후보 반환
내부적으로 1차 모델이 애매(ambiguous)로 판정한 건은 변별력이 더 높은 2차 모델(buffalo_l)로 자동 재검증합니다.
2차가 확정하면 recognized: true + verified_by: "buffalo_l"로 응답합니다(앱은 그대로 두어도 됩니다 — 필드만 추가).
일란성 쌍둥이처럼 얼굴만으로 자동 확정이 불가한 경우에는 recognized는 false로 두되,
아래처럼 후보 명단(candidates)을 함께 내려줍니다. 도입사에서 이 후보로 최종 선택 UI(둘 중 택1 등)를 구성할 수 있습니다.
{
"status": "success",
"recognized": false, // 자동 확정 안 함
"ambiguous": true,
"verified_by": null,
"gap": 0.012,
"message": "닮은 사람과 구분이 어렵습니다. 한 번 더 촬영해 주세요.",
"candidates": [ // '이 중 하나' 후보(top-2)
{ "unique_key": "member-1024", "group_key": "A",
"extra": { "name": "홍길동" }, "similarity": 0.712 },
{ "unique_key": "member-1025", "group_key": "A",
"extra": { "name": "홍길순" }, "similarity": 0.700 }
]
}
candidates는ambiguous: true일 때만 포함됩니다.- 각 후보 형태는
person과 동일 +similarity(유사도) 추가. recognized는 여전히 false — 서버는 자동 확정하지 않습니다.- 후보로 최종 선택 UI를 만들지, 재촬영을 유도할지는 도입사 정책입니다.
- 기존 앱은 모르는 필드를 무시하므로 하위호환됩니다.
POST 다중탐지 · N:N
이미지 속 모든 얼굴을 탐지해 각자 1:N 매칭합니다. CCTV·단체 프레임에서 "지금 화면에 누가 보이는지" 명단을 한 번에 받습니다.
요청 파라미터 · multipart/form-data
| 필드 | 타입 | 설명 |
|---|---|---|
| file 필수 | file | 탐지할 이미지 |
| group_key 선택 | string | 지정 시 해당 그룹에 등록된 얼굴만 후보로 매칭 |
| threshold 선택 | float | 매칭 임계값. 미지정 시 회사 설정 → 전역 기본(0.35) |
| license_key 선택 | string | 회사코드 (헤더 미사용 시) |
curl -X POST https://facematch.team1985.com/api/multi-detect \
-H "X-License-Key: <회사코드>" \
-F file=@crowd.jpg
{
"status": "success",
"num_faces": 3,
"recognized_count": 2,
"elapsed_ms": 880,
"faces": [
{
"recognized": true,
"similarity": 0.987,
"person": {
"unique_key": "member-1024",
"extra": { "name": "홍길동" }
},
"bbox": [88,60,240,250]
},
{
"recognized": false,
"similarity": 0.21,
"person": null,
"bbox": [410,90,560,270]
}
]
}
GET 등록 목록
자기 회사에 등록된 얼굴 전체 목록입니다. 부가정보는 복호화되어 내려가고, name/mobile은 부가정보에서 병합된 값입니다.
curl https://facematch.team1985.com/api/faces \
-H "X-License-Key: <회사코드>"
{
"status": "success",
"list": [
{
"id": 12,
"unique_key": "member-1024",
"group_key": "A",
"extra": { "name": "홍길동", "mobile": "01012345678" },
"name": "홍길동",
"mobile": "01012345678",
"image_path": "/images/reg_....jpg",
"created_at": 1782301234
}
]
}
GET 등록 상세 · 방향별 이미지
등록 얼굴 1건의 방향별 등록 이미지와 복호화된 부가정보를 조회합니다.
curl https://facematch.team1985.com/api/faces/12/poses \
-H "X-License-Key: <회사코드>"
{
"status": "success",
"unique_key": "member-1024",
"group_key": "A",
"extra": { "name": "홍길동" },
"list": [
{ "pose": "frontal", "image_path": "/images/...", "quality": 312.5 },
{ "pose": "left_up", "image_path": "/images/...", "quality": 287.1 }
]
}
POST 등록 수정
이름·연락처를 수정합니다. 값은 암호화된 부가정보에 병합 저장되며, 임베딩·사진은 유지됩니다.
| 필드 | 타입 | 설명 |
|---|---|---|
| name 선택 | string | 이름. 빈 값이면 부가정보에서 제거 |
| mobile 선택 | string | 연락처. 빈 값이면 부가정보에서 제거 |
curl -X POST https://facematch.team1985.com/api/faces/12 \
-H "X-License-Key: <회사코드>" \
-F name=홍길동 -F mobile=01012345678
DELETE 등록 삭제
등록 얼굴을 삭제합니다. 삭제 즉시 인식 대상에서 제외되며, 과거 매칭 로그의 증적 이미지는 보존됩니다(소프트 삭제). 등록 시 사용한 unique_key(식별키)로 삭제하는 것이 기본이며, 내부 face_id를 아는 경우 ID 삭제도 가능합니다.
curl -X DELETE "https://facematch.team1985.com/api/faces/by-key/member-1024?group_key=gangnam" \
-H "X-License-Key: <회사코드>"
# 응답
{ "status": "success", "deleted": 1, "face_ids": [12] }
group_key는 옵션입니다 — 전달하면 해당 그룹 스코프의 등록만 삭제하고, 생략하면 회사 내 해당 식별키의 등록을 모두 삭제합니다(응답의 deleted가 삭제 건수). 일치하는 등록이 없으면 404를 반환합니다.curl -X DELETE https://facematch.team1985.com/api/faces/12 \
-H "X-License-Key: <회사코드>"
음파 측정 · 개요
매장 스피커가 사람 귀에 들리지 않는 초음파로 구역 ID를 방송하고, 고객 앱이 그것을 듣고 방문·체류를 기록합니다. 오프라인 캠페인의 노출과 방문을 잇는 용도이며, 본인인증 수단이 아닙니다.
얼굴인식과 같은 회사코드(license_key)를 그대로 사용합니다. 별도 키 발급이 필요 없습니다.
동작 구조
매장 스피커 ──(초음파 존ID, 1.68초마다 반복)──▶ 고객 앱 마이크
│
진입/이탈이 확정될 때만 ▼
POST /api/sonic/event
복조기는 존ID를 1.68초마다 계속 읽지만, 서버로는 진입·이탈 두 시점에만 보냅니다. 광고주가 보는 지표는 수신 횟수가 아니라 방문 수와 체류 시간이기 때문입니다.
신호 규격
| 항목 | 값 |
|---|---|
| 변조 | BFSK 연속위상 · 0 = 18kHz / 1 = 21kHz |
| 샘플레이트 | 44,100 Hz |
| 심볼 길이 | 30 ms |
| 프레임 | 프리앰블 16b + SFD 8b + 존ID 24b + CRC-8 8b = 56b (1.68초) |
| 존ID 범위 | 0 ~ 16,777,215 |
존 등록과 송출 음원
존은 고객사가 API로 직접 등록합니다. 얼굴등록과 같은 방식으로, 고객사가 정한 식별키(zone_key)로 존을 만들면 방송용 24비트 zone_id는 서버가 배정해 돌려줍니다 — 숫자 공간을 직접 관리할 필요가 없습니다.
등록한 존의 송출용 WAV를 API로 내려받을 수 있어, 매장에 앱을 설치하지 않고 기존 BGM 시스템이나 사이니지에 그대로 얹을 수 있습니다. 관리 콘솔(/admin → 음파 측정)에서도 같은 작업과 지표 확인이 가능합니다.
UNPROCESSED로 지정하세요. iOS는 백그라운드 마이크 접근이 불가하므로 앱이 열려 있는 동안만 수신됩니다.
POST 존 등록 · 등록 / 갱신
측정할 구역을 등록합니다. 얼굴등록과 같은 구조로, 같은 zone_key로 다시 보내면 갱신됩니다. 이때 zone_id는 유지되므로 이미 매장에서 송출 중인 음원을 다시 만들 필요가 없습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
zone_key 필수 | string | 고객사가 정한 존 식별키. 매장·구역을 구분할 수 있는 값이면 무엇이든 |
name 선택 | string | 구역명. 예: 1층 입구 |
place_name 선택 | string | 매장명. 예: 강남점 |
campaign 선택 | string | 캠페인명. 지표를 캠페인 단위로 묶어 볼 때 사용 |
curl -X POST https://facematch.co.kr/api/sonic/zones \
-H "X-License-Key: <회사코드>" \
-F zone_key=gangnam-1f-entrance \
-F "name=1층 입구" \
-F place_name=강남점 \
-F campaign=여름세일
{
"status": "success",
"action": "inserted", // 또는 "updated"
"zone_key": "gangnam-1f-entrance",
"zone_id": 1001 // 서버가 배정한 방송용 ID
}
GET 존 목록
회사에 등록된 존 목록입니다. 앱은 수신한 존ID를 사람이 읽는 이름으로 바꿔 보여줄 때 사용합니다.
curl https://facematch.co.kr/api/sonic/zones \
-H "X-License-Key: <회사코드>"
{
"status": "success",
"zones": [
{
"zone_id": 1001,
"zone_key": "gangnam-1f-entrance",
"name": "1층 입구",
"place_name": "강남점",
"campaign": "여름세일"
}
]
}
GET 송출 음원 · WAV
존ID를 반복 송출하는 WAV를 내려받습니다. 매장에 별도 송신 장비나 앱이 필요 없습니다 — 기존 BGM 시스템·사이니지에서 이 파일을 반복 재생하면 됩니다.
| 파라미터 | 타입 | 설명 |
|---|---|---|
minutes 선택 | float | 음원 길이(분). 기본 3, 범위 0.5 ~ 30 |
curl "https://facematch.co.kr/api/sonic/zones/by-key/gangnam-1f-entrance/wav?minutes=10" \
-H "X-License-Key: <회사코드>" \
-o zone_gangnam_1f.wav
DELETE 존 삭제
존을 삭제합니다. 이미 쌓인 방문 지표는 그대로 유지됩니다 — 캠페인이 끝나 존을 정리해도 지난 실적 보고 근거는 남아야 하기 때문입니다.
curl -X DELETE https://facematch.co.kr/api/sonic/zones/by-key/gangnam-1f-entrance \
-H "X-License-Key: <회사코드>"
# 응답
{ "status": "success", "deleted": 1, "zone_id": 1001 }
POST 방문 이벤트 · 진입 / 이탈
존 진입과 이탈을 보고합니다. Content-Type: application/json이며, 다른 API와 달리 폼이 아닌 JSON 본문을 받습니다.
파라미터
| 이름 | 타입 | 설명 |
|---|---|---|
license_key | string | 회사코드. X-License-Key 헤더로도 가능 |
device_id | string | 앱 설치 식별자. 순 방문자(UV) 집계 기준 |
zone_id | int | 수신한 존ID. 콘솔에 등록되지 않은 값은 404 |
event | string | enter 또는 exit |
entered_at | int | 진입 시각(epoch 초). 진입·이탈을 잇는 상관키라 두 요청에서 같은 값 |
last_seen_at | int | 마지막 수신 시각(epoch 초). exit 에만 |
dwell_sec | int | 체류 시간(초). exit 에만 |
frame_count | int | 수신한 프레임 수. 신호 품질 참고용 |
avg_snr_db | float | 평균 심볼 SNR(dB). 신호 품질 참고용 |
curl -X POST https://facematch.co.kr/api/sonic/event \
-H "Content-Type: application/json" \
-d '{
"license_key": "<회사코드>",
"device_id": "a3f9-...",
"zone_id": 1001,
"event": "enter",
"entered_at": 1787570000
}'
curl -X POST https://facematch.co.kr/api/sonic/event \
-H "Content-Type: application/json" \
-d '{
"license_key": "<회사코드>",
"device_id": "a3f9-...",
"zone_id": 1001,
"event": "exit",
"entered_at": 1787570000,
"last_seen_at": 1787570120,
"dwell_sec": 120,
"frame_count": 70,
"avg_snr_db": 19.4
}'
GET 매칭 로그
최근 인식 시도 이력입니다. 어떤 방향(matched_pose) 등록 이미지가 매칭을 이겼는지, 시도 사진(probe_image)과 매칭된 등록 사진(matched_image)까지 증적으로 제공됩니다.
curl "https://facematch.team1985.com/api/logs?limit=50" \
-H "X-License-Key: <회사코드>"
{
"status": "success",
"list": [
{
"id": 224,
"mode": "detect", // detect | multi
"recognized": true,
"name": "홍길동",
"unique_key": "member-1024",
"group_key": "A",
"similarity": 0.723,
"threshold": 0.35,
"matched_pose": "left_up",
"probe_image": "/images/probe_....jpg",
"matched_image": "/images/reg_....jpg",
"elapsed_ms": 180,
"num_faces": 1,
"created_at": 1782301234
}
]
}
https://facematch.team1985.com · © 2026 team1985