> ## Documentation Index
> Fetch the complete documentation index at: https://developers.argosidentity.com/llms.txt
> Use this file to discover all available pages before exploring further.

# FaceAuth 공통 참조

> FaceAuth API가 공통으로 사용하는 FaceAuthSubmission 객체와 오류 응답 형식, 그리고 구 엔드포인트(/v3/faceauth)에서 신 엔드포인트(/v3/face-auth)로 옮길 때의 변경 사항을 정리합니다.

<Warning>
  **FaceAuth API가 `/v3/face-auth`로 개편되었습니다.**

  구 엔드포인트 `/v3/faceauth`와 응답 구조·필드 이름·오류 형식이 다릅니다. 기존 연동을 옮기려면 이 페이지의 [구 API에서 옮기기](#구-api에서-옮기기)를 먼저 확인하세요.
</Warning>

이 페이지는 여러 엔드포인트가 함께 사용하는 정의를 한곳에 모은 참조 문서입니다. 각 엔드포인트 페이지는 이 정의를 이름으로 참조하며, 엔드포인트마다 달라지는 부분만 해당 페이지에 적혀 있습니다.

* [FaceAuthSubmission 객체](#faceauthsubmission-객체) — [POST](/ko/idcheck/add-on/post-faceauth) 생성 응답, [목록 조회](/ko/idcheck/add-on/get-faceauth)의 `items[]` 요소, [단건 조회](/ko/idcheck/add-on/get-faceauth_detail) 응답이 모두 사용합니다.
* [오류 응답](#오류-응답) — 모든 엔드포인트가 공통으로 사용하는 오류 형식입니다.
* [구 API에서 옮기기](#구-api에서-옮기기) — `/v3/faceauth` 연동을 `/v3/face-auth`로 옮길 때의 변경 사항입니다.

## FaceAuthSubmission 객체

FaceAuth 제출 한 건의 정보를 담은 객체입니다. 제출의 인증 결과, 판정에 사용한 정책, 판정 점수를 확인할 수 있습니다.

<Warning>
  **응답에서 field를 생략하지 않습니다.**

  적용되지 않은 정책, 수집되지 않은 결과, 해당 없는 배열은 key를 빼는 대신 `null`을 채워 반환합니다. 따라서 응답 본문의 key 집합은 제출 종류나 프로젝트 설정과 무관하게 **항상 같으며**, `'field' in obj`나 `undefined` 검사 없이 `value === null` 한 가지 방식으로만 확인하면 됩니다.

  타입에 `nullable`이 붙은 field가 `null`이 될 수 있는 field입니다.
</Warning>

### 최상위 field

<ResponseField name="authId" type="string">
  FaceAuth submission의 키값입니다. [단건 조회](/ko/idcheck/add-on/get-faceauth_detail)·[이미지 다운로드](/ko/idcheck/add-on/get-faceauth_image)·[삭제](/ko/idcheck/add-on/delete-faceauth) API에 사용하므로 반드시 저장하세요.
</ResponseField>

<ResponseField name="authStatus" type="string">
  인증 결과입니다. `approved`, `rejected` 중 하나입니다.
</ResponseField>

<ResponseField name="createTime" type="string">
  제출 생성 시각입니다. `yyyy-MM-dd'T'HH:mm:ss.SSSZ` RFC 3339 UTC 형식입니다. (예: `2026-08-11T10:09:14.028Z`)
</ResponseField>

<ResponseField name="submitType" type="string">
  제출 방식입니다. `form`은 Face Auth URL 화면, `api`는 [POST /v3/face-auth](/ko/idcheck/add-on/post-faceauth)로 제출된 건입니다.
</ResponseField>

<ResponseField name="kycSubmissionId" type="string">
  비교 기준이 된 KYC submission의 ID입니다.
</ResponseField>

<ResponseField name="deleteTime" type="nullable · string">
  삭제 완료 시각입니다. RFC 3339 UTC 형식이며, 삭제되지 않은 제출은 `null`입니다.
</ResponseField>

<ResponseField name="policy" type="object">
  판정에 사용한 정책입니다. 하위 6개 항목은 항상 포함됩니다. 아래 [policy 항목](#policy-항목)을 참고하세요.
</ResponseField>

<ResponseField name="result" type="object">
  판정 결과입니다. 하위 6개 field는 항상 포함됩니다. 아래 [result field](#result-field)를 참고하세요.
</ResponseField>

<ResponseField name="signals" type="nullable · object">
  Face Auth URL 화면에서 수집한 보조 정보입니다. 아래 [signals field](#signals-field)를 참고하세요.
</ResponseField>

<ResponseField name="rejectComment" type="nullable · array">
  거절 사유 문자열 배열입니다. `approved` 제출은 `null`입니다.
</ResponseField>

<ResponseField name="failCode" type="nullable · array">
  거절 코드 문자열 배열입니다. `approved` 제출은 `null`입니다.
</ResponseField>

<Note>
  [POST /v3/face-auth](/ko/idcheck/add-on/post-faceauth) 생성 응답에는 `policy`가 포함되지 않습니다. 나머지 field는 동일합니다.
</Note>

### policy 항목

처리 시점의 프로젝트 설정 snapshot입니다. 이후 프로젝트 설정을 변경해도 이미 생성된 제출의 값은 바뀌지 않습니다.

6개 항목(`faceSimilarity`, `occluded`, `faceCover`, `headCover`, `liveness`, `activeLiveness`) 모두 아래 두 field를 갖습니다.

<ResponseField name="enabled" type="boolean">
  제출 처리 시점에 이 정책이 켜져 있었는지 여부입니다. 정책 적용 여부는 `threshold` 값이 아니라 이 값으로 판정합니다.
</ResponseField>

<ResponseField name="threshold" type="nullable · number">
  판정에 사용한 숫자 기준입니다. `enabled`가 `false`이거나 숫자 기준을 쓰지 않는 정책이면 `null`입니다.
</ResponseField>

| 항목               | 정책               | 통과 조건                                                                     |
| ---------------- | ---------------- | ------------------------------------------------------------------------- |
| `faceSimilarity` | 얼굴 유사도           | `result.faceSimilarity`가 `threshold` 이상                                   |
| `occluded`       | 얼굴 가림 금지         | 얼굴이 가려지지 않음. 숫자 기준을 쓰지 않아 `threshold`가 항상 `null`                          |
| `faceCover`      | 얼굴 보호장비 착용 요구    | 마스크·고글 등이 감지되고 신뢰도가 `threshold` 이상                                        |
| `headCover`      | 머리 보호장비 착용 요구    | 안전모 등이 감지되고 신뢰도가 `threshold` 이상                                           |
| `liveness`       | Passive Liveness | `result.livenessScore`가 `threshold` 이상                                    |
| `activeLiveness` | Active Liveness  | `result.activeLivenessScore`가 `threshold` 이상. `submitType: form` 제출에서만 실행 |

<Warning>
  **`occluded`와 `faceCover`·`headCover`는 목적이 반대입니다.**

  `occluded`는 얼굴을 가리는 것을 금지하고, `faceCover`·`headCover`는 보호장비 착용을 요구합니다. 이름이 비슷하지만 통과 조건이 서로 반대입니다. 산업 현장처럼 보호장비 착용이 필요한 환경을 위한 정책입니다.
</Warning>

<Note>
  **`enabled`는 프로젝트 설정이지 실행 결과가 아닙니다.**

  정책이 켜져 있어도 제출 방식에 따라 실행하지 않을 수 있습니다. 실제로 실행했는지는 `result`의 대응 field로 판단하며, `null`이면 실행하지 않은 것입니다. 현재 이 차이가 나타나는 곳은 `activeLiveness` 하나입니다. `submitType: api` 제출은 `enabled: true`여도 `result.activeLivenessScore`가 `null`이며 이는 정상입니다.
</Note>

### result field

실행하지 않은 정책의 결과 field는 `null`입니다. 예를 들어 `policy.liveness.enabled`가 `false`인 제출은 `livenessScore`가 `null`입니다.

<ResponseField name="faceSimilarity" type="nullable · number">
  얼굴 유사도 점수입니다. 0 이상 100 이하이며 소수를 포함할 수 있습니다.
</ResponseField>

<ResponseField name="livenessScore" type="nullable · number">
  Passive Liveness 점수입니다.
</ResponseField>

<ResponseField name="activeLivenessScore" type="nullable · number">
  Active Liveness 점수입니다. `submitType: api` 제출은 항상 `null`입니다.
</ResponseField>

<ResponseField name="occluded" type="nullable · object">
  얼굴 가림 감지 결과입니다. `value: true`면 가려진 것으로 판정해 거절됩니다.

  <Expandable title="Properties">
    <ResponseField name="value" type="nullable · boolean">
      감지 여부입니다.
    </ResponseField>

    <ResponseField name="confidence" type="nullable · number">
      판정 신뢰도입니다. 0 이상 100 이하입니다.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="faceCover" type="nullable · object">
  얼굴 보호장비 감지 결과입니다. `value: false`면 미착용으로 판정해 거절됩니다. 하위 field는 `occluded`와 같습니다.
</ResponseField>

<ResponseField name="headCover" type="nullable · object">
  머리 보호장비 감지 결과입니다. `value: false`면 미착용으로 판정해 거절됩니다. 하위 field는 `occluded`와 같습니다.
</ResponseField>

<Note>
  `occluded`, `faceCover`, `headCover` 세 항목은 같은 구조를 갖습니다. 검사를 실행하지 않았으면 \*\*객체 전체가 `null`\*\*이며, 하위 field만 `null`인 형태로는 반환하지 않습니다.
</Note>

### signals field

Face Auth URL 화면에서 수집한 보조 정보입니다. 수집한 값이 있는 `submitType: form` 제출에만 값이 있고, `submitType: api` 제출과 수집값이 없는 제출은 \*\*객체 전체가 `null`\*\*입니다. 참고용이며 인증 결과 판정에는 사용하지 않습니다.

<ResponseField name="startButtonClickTime" type="nullable · string">
  사용자가 시작 버튼을 누른 시각입니다. RFC 3339 UTC 형식입니다.
</ResponseField>

<ResponseField name="cameraProcessInfo" type="nullable · array">
  카메라 촬영 구간 기록입니다. 기록이 없으면 빈 배열이 아니라 `null`입니다.

  <Expandable title="Properties">
    <ResponseField name="[i].processStartTime" type="nullable · string">
      촬영 구간 시작 시각입니다. RFC 3339 UTC 형식입니다.
    </ResponseField>

    <ResponseField name="[i].processEndTime" type="nullable · string">
      촬영 구간 종료 시각입니다. RFC 3339 UTC 형식입니다.
    </ResponseField>

    <ResponseField name="[i].type" type="nullable · string">
      촬영 구간의 종류입니다. `faceAuth-passive`, `faceAuth-active`, `faceAuth-auto-capture` 등이 들어갑니다. 값 목록은 고정하지 않으며 예고 없이 추가되므로 알 수 없는 값은 무시하도록 처리하세요.
    </ResponseField>

    <ResponseField name="[i].error" type="nullable · string">
      해당 구간에서 발생한 오류입니다. 오류가 없으면 `null`입니다. 사용자 브라우저에서 수집한 메시지라 형식이 고정되지 않으므로 분기에 사용하지 마세요.
    </ResponseField>
  </Expandable>
</ResponseField>

### rejectComment와 failCode

`rejectComment`는 사람이 읽는 설명이고 `failCode`는 분기용 코드입니다.

<Warning>
  **거절 원인 분기는 `failCode`로 하세요.** `rejectComment` 문구는 사전 고지 없이 바뀌고, 서로 다른 원인이 같은 문구를 공유하기도 합니다. `Please retry with another face image.`는 `liveness_fail`과 `active_liveness_fail`이 함께 사용합니다.
</Warning>

두 배열은 **같은 index끼리 대응하며 길이가 항상 같습니다.** `failCode[0]`의 사유가 `rejectComment[0]`입니다.

```json result.json theme={null}
{
  "rejectComment": [
    "Protection equipment is not found on Head.",
    "face compare similarity score is lower than threshold"
  ],
  "failCode": ["Head_cover_fail", "face_compare_underscore"]
}
```

하나의 제출이 여러 정책에서 동시에 실패할 수 있으므로 배열의 모든 값을 확인하세요. 배열 순서는 정책 평가 순서를 따르며 고정된 값이 아닙니다. 특정 코드가 항상 첫 번째에 온다고 가정하지 마세요.

`failCode` 값 목록은 [POST /v3/face-auth — 거절 코드](/ko/idcheck/add-on/post-faceauth#5-3-거절-코드-failcode)를 참고하세요.

### 파싱 시 유의할 점

* 응답의 key 집합은 항상 같습니다. 값이 없어도 `null`로 채워져 반환되므로 `undefined` 검사나 key 존재 검사는 필요하지 않습니다.
* `policy.*.threshold`의 `0`은 유효한 설정값입니다. falsy 검사로 설정 여부를 판정하면 안 되고 `enabled`로 판정하세요.
* 배열 field는 값이 없을 때 빈 배열이 아니라 `null`입니다. 순회 전에 `?? []`로 기본값을 주거나 `!== null`을 확인하세요.
* 중첩 객체(`occluded`, `faceCover`, `headCover`, `signals`)는 해당 없을 때 객체 전체가 `null`입니다. 하위 field를 읽기 전에 부모가 `null`인지 먼저 확인하세요.
* threshold와 score가 같으면 해당 threshold를 충족한 것으로 판정합니다.
* 점수는 정수가 아니라 소수를 포함할 수 있습니다. 문자열이 아닌 number로 파싱하세요.

## 오류 응답

아르고스 애플리케이션이 반환하는 오류는 아래 형식을 사용합니다.

```json error.json theme={null}
{
  "code": "REQUEST_INVALID",
  "message": "limit must be a decimal integer between 10 and 200."
}
```

<ResponseField name="code" type="string">
  오류 코드입니다. 분기 처리는 HTTP 상태 코드가 아니라 이 값으로 하세요.
</ResponseField>

<ResponseField name="message" type="string">
  사람이 읽는 오류 설명입니다. 문구는 사전 고지 없이 바뀔 수 있으므로 문자열 비교로 분기하면 안 됩니다.
</ResponseField>

에러 객체의 field는 `code`와 `message` 둘뿐입니다.

### 인증 계층 오류 (401, 403)

<Warning>
  **인증 계층에서 차단된 요청은 `{ code, message }` 형식을 따르지 않습니다.**

  API key 검증은 아르고스 애플리케이션에 도달하기 전 인증 계층에서 수행됩니다. 이 단계에서 차단된 응답에는 `code` field가 없습니다.
</Warning>

| 상황                                    | HTTP status | 응답 본문                                            |
| ------------------------------------- | ----------- | ------------------------------------------------ |
| API key를 전달하지 않음                      | `401`       | `{ "message": "Unauthorized" }`                  |
| 존재하지 않거나 유효하지 않은 API key              | `403`       | `{ "message": "..." }`                           |
| 유효한 API key이지만 FaceAuth 프로젝트에 연결되지 않음 | `403`       | `{ "code": "AUTH_FORBIDDEN", "message": "..." }` |

* **`401`과 인증 계층 `403`은 `code` 기반 분기가 아니라 HTTP status로 처리하세요.** 이 두 응답에는 `code` field가 없습니다.
* `403`은 원인이 두 가지이고 응답 형식이 다릅니다. `code` field가 있으면 애플리케이션이 반환한 것이고, 없으면 인증 계층이 차단한 것입니다.
* 인증 계층 오류의 `message`는 아르고스가 생성하지 않으며 사전 고지 없이 바뀔 수 있습니다. 문구를 파싱하지 마세요.

<Note>
  기술 문의나 장애 신고 시에는 요청 시각(UTC), 호출한 endpoint, 사용한 API key의 앞 4자리를 함께 전달해 주세요.
</Note>

### 오류 코드 목록

| HTTP status | code                            | 발생 조건                                            | 발생 endpoint        |
| ----------- | ------------------------------- | ------------------------------------------------ | ------------------ |
| `400`       | `REQUEST_INVALID`               | 유효하지 않은 요청입니다. 각 endpoint 페이지의 오류 응답 절을 참고하세요.   | 전체                 |
| `400`       | `FACEAUTH_FACE_NOT_DETECTED`    | 얼굴 이미지에서 비교 가능한 얼굴을 찾지 못했습니다.                    | POST               |
| `403`       | `AUTH_FORBIDDEN`                | API key는 유효하지만 FaceAuth 프로젝트에 연결되지 않았습니다.        | 전체                 |
| `404`       | `FACEAUTH_SUBMISSION_NOT_FOUND` | 해당 프로젝트에서 `authId`를 찾을 수 없습니다.                   | 단건 조회, 이미지, DELETE |
| `404`       | `SUBMISSION_NOT_FOUND`          | KYC submission이 없거나 삭제됐거나 다른 프로젝트에 속합니다.         | POST               |
| `409`       | `SUBMISSION_NOT_APPROVED`       | KYC submission이 승인되지 않았거나 reference image가 없습니다. | POST               |
| `503`       | `UPSTREAM_UNAVAILABLE`          | 내부 서비스를 일시적으로 사용할 수 없습니다. 잠시 후 동일 요청을 재시도하세요.    | 전체                 |
| `503`       | `UPSTREAM_INVALID_RESPONSE`     | 내부 조회 결과를 공개 계약으로 변환할 수 없습니다.                    | 목록·단건 조회           |
| `500`       | `INTERNAL_UNEXPECTED`           | 예상하지 못한 서버 오류입니다.                                | 전체                 |

## 구 API에서 옮기기

구 엔드포인트 `/v3/faceauth`를 연동 중이라면 아래 변경 사항을 확인하세요. **path·필드 이름·응답 봉투·오류 형식이 모두 바뀌었습니다.**

### 엔드포인트

| 동작       | 구 API                               | 신 API                              |
| -------- | ----------------------------------- | ---------------------------------- |
| 제출 생성    | `POST /v3/faceauth`                 | `POST /v3/face-auth`               |
| 목록 조회    | `GET /v3/faceauth`                  | `GET /v3/face-auth`                |
| 단건 조회    | `GET /v3/faceauth?authId=...`       | `GET /v3/face-auth/{authId}`       |
| 이미지 다운로드 | `GET /v3/faceauth/image?authId=...` | `GET /v3/face-auth/{authId}/image` |
| 제출 삭제    | `DELETE /v3/faceauth?authId=...`    | `DELETE /v3/face-auth/{authId}`    |

`authId`는 query parameter가 아니라 **path parameter**가 되었고, 목록 조회와 단건 조회가 **별도 endpoint**로 분리되었습니다.

### 응답 봉투

| 구분             | 구 API                                                   | 신 API                                 |
| -------------- | ------------------------------------------------------- | ------------------------------------- |
| 단건 조회          | `{ faceAuth_projectId, data: [ {...} ] }`               | 제출 객체가 그대로 최상위                        |
| 목록 조회          | `{ faceAuth_projectId, data: { items, nextPage_key } }` | `{ items, nextCursor }`               |
| 없는 `authId` 조회 | `200` + `data: []`                                      | `404 FACEAUTH_SUBMISSION_NOT_FOUND`   |
| 제출 생성          | `200` + `{ score, authentication_id, ... }`             | `201 Created` + FaceAuthSubmission 객체 |
| 제출 삭제          | `{ "result": "success", "statusCode": 200 }`            | `{ "authId": "..." }`                 |

`faceAuth_projectId`와 `statusCode`는 응답에서 제거되었습니다.

### 필드 이름

| 구 API                                       | 신 API                          |
| ------------------------------------------- | ------------------------------ |
| `auth_id` / `authentication_id`(POST)       | `authId`                       |
| `auth_status`                               | `authStatus`                   |
| `create_time`                               | `createTime`                   |
| `submit_type`                               | `submitType`                   |
| `kyc_submission_id`                         | `kycSubmissionId`              |
| `reject_comment` / `rejected_comment`(POST) | `rejectComment`                |
| `fail_code`                                 | `failCode`                     |
| `score`(POST, snake\_case)                  | `result`(camelCase, 조회와 동일)    |
| `delete_check` + `delete_time`              | `deleteTime` 하나 (삭제 전은 `null`) |

### 구조 변경

<AccordionGroup>
  <Accordion title="policy — 평면 임계값에서 중첩 객체로">
    구 API는 `faceSimilarity_threshold: 85`처럼 임계값만 평면으로 담았고, `occluded_threshold`만 `boolean`이었습니다.

    신 API는 항목마다 `{ enabled, threshold }` 객체를 갖습니다. **정책 사용 여부는 `threshold` 값이 아니라 `enabled`로 판정합니다.** `occluded`는 숫자 기준을 쓰지 않으므로 `threshold`가 항상 `null`입니다.

    ```json 구 API theme={null}
    { "faceSimilarity_threshold": 85, "occluded_threshold": true }
    ```

    ```json 신 API theme={null}
    {
      "faceSimilarity": { "enabled": true, "threshold": 85 },
      "occluded": { "enabled": true, "threshold": null }
    }
    ```
  </Accordion>

  <Accordion title="result — occluded·faceCover·headCover가 객체로">
    구 API는 `occluded: false`(boolean), `headCover: 0`(number)처럼 값 하나만 반환했습니다. 신 API는 세 항목 모두 `{ value, confidence }` 객체이며, 검사를 실행하지 않았으면 객체 전체가 `null`입니다.

    ```json 신 API theme={null}
    { "occluded": { "value": false, "confidence": 99.2 }, "faceCover": null }
    ```
  </Accordion>

  <Accordion title="페이지네이션 — 복합 키에서 단일 cursor로">
    구 API는 `nextPage_key`의 `authId`·`createTime`을 각각 `nextKey_id`·`nextKey_date`로 되돌려 보냈고, 조회 개수는 `count`(1\~2,000, 기본 2,000)였습니다.

    신 API는 서명된 단일 문자열 `nextCursor`를 그대로 `cursor`에 전달하며, 조회 개수는 `limit`(10\~200, 기본 100)입니다. `limit`은 범위를 벗어나면 무시되지 않고 `400 REQUEST_INVALID`가 반환됩니다.
  </Accordion>

  <Accordion title="이미지 다운로드 — 바이너리 직접 반환에서 302 redirect로">
    구 API는 JPEG 바이너리를 본문으로 직접 반환했습니다. 신 API는 `302 Found`와 `Location` header로 만료 시간이 짧은 다운로드 URL을 돌려주므로, **HTTP client가 redirect를 따라가도록 설정**해야 합니다 (`curl --location`).
  </Accordion>

  <Accordion title="오류 형식 — 세 가지 형식에서 하나로">
    구 API는 endpoint마다 `{ message, errorCode, statusCode }`, `{ traceId, errorCode, message }`, `{ message, statusCode }`가 섞여 있었고 이미지 endpoint만 인증 실패를 모두 `403`으로 반환했습니다.

    신 API는 애플리케이션 오류를 모두 `{ code, message }`로 통일하고, 인증 계층 오류만 예외입니다. 위 [오류 응답](#오류-응답)을 참고하세요.
  </Accordion>
</AccordionGroup>

### 가장 주의할 변경

<Warning>
  **구 API는 사용하지 않는 옵션의 key를 응답에서 뺐지만, 신 API는 key를 항상 포함하고 값에 `null`을 채웁니다.**

  구 API 기준으로 `if (result.headCover)` 또는 `'headCover' in result`처럼 **key 존재 여부로 정책 사용 여부를 판정하던 코드는 신 API에서 오작동합니다.** 신 API에서는 `result.headCover !== null`(실행 여부) 또는 `policy.headCover.enabled`(설정 여부)로 판정하세요.
</Warning>
