{"openapi":"3.1.0","info":{"title":"바이오 분산관리 센터 Open API (PoC 모의)","description":"연계기관이 분할 보관 조각(receipt)을 **저장·조회·암호 파기**하고 **호환 인증 현황**을 확인하는 API 입니다.\n**PoC 모의 환경이며 실제 금융결제원 시스템이 아닙니다.** 이 문서의 예시는 모두 가상의 값입니다.\n\n## 인증(3중)\n1. **mTLS** — 운영 구성에서는 연계 전용 포트(기본 9143)에서 클라이언트 인증서가 필수이고, 인증서 CN 은 기관에 등록된 값과 같아야 합니다.\n2. **요청 서명** — `health` 를 제외한 모든 요청에 헤더 4개가 필요합니다: `X-Inst-Code`(기관 코드), `X-Timestamp`(epoch **초**), `X-Nonce`(요청마다 새 값, `[A-Za-z0-9_.:-]{8,80}`), `X-Signature`.\n   `X-Signature` = HMAC-SHA256(**인증키**, `METHOD\\n경로?쿼리\\n타임스탬프\\nnonce\\nSHA-256(본문)`)의 **소문자 hex**.\n   서명 대상 경로·쿼리는 실제로 보내는 문자열 그대로(`/open-api/v1/...` 전체)여야 하고, 본문이 없으면 빈 본문의 해시를 씁니다.\n3. **재전송 방지** — `X-Timestamp` 가 센터 시각과 ±60초(경계 포함) 이내여야 하고(서명은 만든 뒤 최대 약 2분까지 시각 검사를 통과할 수 있습니다), nonce 는 기관별로 약 5분 동안 기억하며 그 안에서 한 번만 쓸 수 있습니다.\n\n허용 IP 가 등록된 기관은 허용 대역 밖 호출이 `AUTH_FAILED` 로 거부됩니다(**센터가 보는 접속 주소 기준** — 로드밸런서 뒤에서는 신뢰 프록시의 `X-Forwarded-For`, mTLS 를 L4 로 통과시키는 구성에서는 원 IP 를 볼 수 없으므로 LB·방화벽 ACL 에서 강제해야 합니다). 인증키를 재발급하면 **이전 키가 설정된 유예 시간(기본 10분) 동안 함께 허용**되어 무중단으로 교체할 수 있습니다.\n보안상 인증 실패의 사유(키·시각·허용 IP·정지)는 호출자에게 구분해서 알려 주지 않습니다. 같은 `401 AUTH_FAILED` 에는 이 밖에도 인증 헤더 누락, `X-Nonce` 형식 위반,\n`X-Timestamp` 가 숫자가 아니거나 밀리초 단위인 경우, 요청 본문이 1 MiB 를 넘는 경우(413 이 아님), PUT·`compat/report` 본문의 `institutionCode` 누락·불일치가 포함됩니다.\n\n## Swagger UI 에서 호출해 보기(Try it out)\n요청마다 서명이 달라지므로 Swagger UI 의 **Authorize** 에 값을 한 번 넣는 방식으로는 호출되지 않습니다. 다음 순서로 하세요.\n1. 포털의 **개발자 문서 → 서명 생성기**(`/portal/docs#signer`)를 엽니다. 브라우저 안에서만 계산하며 인증키는 서버로 전송되지 않습니다.\n2. 호출하려는 **방식·경로(쿼리 포함)·본문**을 서명 생성기에 똑같이 입력하고 타임스탬프·nonce 를 만든 뒤 `X-Signature` 를 계산합니다.\n3. 여기 Swagger UI 의 **Authorize** 를 눌러 `X-Inst-Code`, `X-Timestamp`, `X-Nonce`, `X-Signature` 네 값을 붙여 넣고, 같은 경로·본문으로 **Try it out → Execute** 합니다.\n4. 서명은 `X-Timestamp` 가 센터 시각 **±60초 안**일 때만 유효하고(서명 후 최대 약 2분), nonce 는 **한 번만** 쓸 수 있습니다. 다시 호출하려면 타임스탬프·nonce·서명을 새로 만드세요.\n운영 구성(`secure` 프로파일)에서는 Open API 가 mTLS 포트에서만 열리므로, 브라우저의 Swagger UI 로는 호출할 수 없고 명세 열람용입니다.\n\n## 등록은 2단계: 저장(PUT) → 확인(confirm)\n기관이 등록 중 응답을 못 받아 실패 처리해도 센터에 소유자 없는 receipt 가 남지 않도록, `PUT /receipts/{id}` 는 receipt 를 **확인 대기(PENDING)** 로 저장하고(조회·인증 불가, GET 404),\n기관이 자기 쪽 등록을 마친 뒤 **`POST /receipts/{id}/confirm`**(본문 없음, 멱등)을 호출해야 **ACTIVE** 가 됩니다. 확인 기한(기본 300초) 안에 확인하지 않으면 센터가 암호 파기합니다.\nconfirm 이 404(없음·만료·파기됨·타 기관)면 새 receiptId 로 PUT 부터 다시 하세요(만료·파기된 receiptId 로 PUT 하면 400, 다른 기관 소유면 409).\n확인 대기 중 같은 receipt 본문(SHA-256 이 같음)의 PUT 재전송은 기한을 늘리고 200 입니다.\n\n## 거래 ID(상관 ID)와 응답 헤더\n- 요청에 **`X-Request-Id`**(예: 거래마다 만든 UUID)를 달면 센터가 그 값을 처리 단계 기록의 상관키로 쓰고 **응답 헤더로 그대로 돌려줍니다**.\n  한 거래가 여러 호출(등록 PUT, 인증 GET, 파기 DELETE …)을 만들면 같은 값을 보내 하나의 거래로 묶으세요. 형식은 `[A-Za-z0-9._:-]{8,40}` 이며 없거나 맞지 않으면 센터가 만듭니다.\n- 모든 응답(오류 포함)에 다음 헤더가 붙습니다: `X-Request-Id`(거래 ID 에코), `X-Center-Node`(처리한 노드), `X-Process-Ms`(센터 내부 처리 시간, 올림 정수 ms),\n  `X-Center-Result`(`STORED`·`UNCHANGED`·`CONFIRMED`·`SERVED`·`DESTROYED`·`NOT_FOUND`·`REJECTED`, 그 밖의 성공 응답은 `OK`). 본문(DTO)은 바뀌지 않았습니다.\n- 시각 필드(`storedAt`·`destroyedAt`·`certifiedAt`·`time`)는 UTC 의 RFC 3339 문자열이며 소수초 자릿수가 가변(0·3·6·9자리)입니다. 길이에 의존하지 말고 RFC 3339 파서로 읽으세요.\n\n## 재시도·멱등 규칙\n- `PUT /receipts/{id}` 와 `DELETE /receipts/{id}` 는 **멱등**입니다. 타임아웃·5xx 후 같은 요청을 그대로 재시도해도 안전합니다(재시도할 때마다 **nonce 와 서명은 새로** 만드세요).\n- `GET` 은 조회 결과(성공·없음·파기·타 기관 소유)마다 접근 이력이 남습니다. 확인 전(PENDING) 조회(404)와 요청 오류(400)는 이력에 남지 않습니다.\n- `429`/`503` 은 지수 백오프로 재시도하고,\n  기관의 호출 한도 초과 외에도 **센터가 평소와 다른 조회 패턴(짧은 시간의 대량·반복 조회, 없는 receipt 연속 조회 등)을 감지하면 해당 기관의 receipt 조회(GET)만 일정 시간 `429 RATE_LIMITED` 로 거부**할 수 있습니다.\n  저장(PUT)·파기(DELETE)·상태 확인은 영향이 없고 사유는 알려 주지 않습니다. 정상적인 인증 흐름(고객 1명당 receipt 1건 조회)에서는 일어나지 않으며, 계속되면 센터 운영 담당에게 요청 ID 와 함께 문의하세요.\n  센터가 계속 응답하지 않으면 **인증을 거부하는 쪽(fail-closed)** 으로 처리하세요.\n- `Retry-After` 는 `503 OVERLOADED`·`503 SERVICE_UNAVAILABLE`·센터 기동 중 503 에만 붙습니다. **`429` 에는 없고**, 시험용 장애 주입으로 나오는 `CENTER_UNAVAILABLE` 에도 없습니다.\n  특히 이상 접근 차단의 `429` 는 기본 300초 동안 이어질 수 있으나 남은 시간을 알 수 없으므로 지수 백오프(예: 1s → 2s → 4s …)로 간격을 늘려 재시도하세요.\n- **`503 OVERLOADED`(5021)** 는 센터가 과부하라 **오래 붙잡지 않고 거부**한 것입니다: 동시 처리 상한을 넘으면 즉시 또는 짧은 대기(기본 최대 0.4초) 뒤에, 내부 DB 연결 풀이 비어 있으면 연결 대기(기본 약 2.5초, 요청당 DB 대기 예산 3초) 뒤에 거부합니다.\n  응답의 `Retry-After`(초) 뒤에 지수 백오프로 재시도하세요(요청은 처리되지 않았으므로 PUT·DELETE 도 안전).\n- **`503 SERVICE_UNAVAILABLE`(5022)** 는 센터가 내부 저장소(DB)에 연결하지 못해 **기다리지 않고 즉시 거부**한 것입니다(`OVERLOADED` 와 달리 의존성 장애). 응답의 `Retry-After`(초) 뒤에 지수 백오프로 재시도하세요(요청은 처리되지 않았으므로 PUT·DELETE 도 안전).\n- 문의할 때는 응답 본문·헤더의 **요청 ID**(`X-Request-Id`)를 알려 주세요.\n\n## 오류 응답\n필터·컨트롤러가 만드는 오류는 `{code, key, message, requestId}` 형식입니다. PUT·POST 에 `Content-Type: application/json` 이 아닌(또는 없는) 본문을 보내면 `415` + `BAD_REQUEST` 입니다.\n존재하지 않는 경로(404)·허용되지 않는 메서드(405)·`Accept` 불일치(406)는 프레임워크 기본 응답(`{timestamp, status, error, path}` 또는 빈 본문·HTML)이며 `X-Center-Result: OK` 가 붙습니다.\n\n- **AUTH_FAILED** (`5011`, HTTP 401) — 인증 실패. 기관 코드·서명·시각·허용 IP·인증서 CN 중 하나가 맞지 않거나 기관이 정지된 상태입니다. 그 밖에 인증 헤더 누락, X-Nonce 형식 위반, X-Timestamp 가 숫자가 아니거나 밀리초 단위인 경우, 요청 본문이 1 MiB 를 넘는 경우(413 이 아님), PUT·compat/report 본문의 institutionCode 누락·불일치도 같은 응답입니다. 보안상 사유는 구분해서 알려 주지 않습니다. _조치:_ 인증키, 서명 대상 경로·쿼리, 서버와의 시각 차이(±60초, X-Timestamp 는 epoch 초), 허용 IP, X-Nonce 형식, 본문 institutionCode 를 확인하세요. 해결되지 않으면 요청 ID 와 함께 센터 운영 담당에게 문의하세요.\n- **REPLAY_DETECTED** (`5004`, HTTP 409) — 같은 nonce 를 다시 사용했습니다. _조치:_ 요청마다 새 nonce 를 만들고 다시 서명해서 보내세요. **[재시도 가능]**\n- **RATE_LIMITED** (`5006`, HTTP 429) — 기관별 분당 호출 한도를 넘었습니다. 센터가 평소와 다른 조회 패턴(짧은 시간의 대량·반복 조회 등)을 감지했을 때도 같은 응답으로 receipt 조회(GET)가 일정 시간 거부될 수 있습니다(사유는 알려 주지 않음). _조치:_ 429 에는 Retry-After 헤더가 없습니다. 이상 접근 차단은 기본 300초 이어질 수 있고 남은 시간을 알 수 없으니 지수 백오프로 간격을 늘려 재시도하세요. 정상 업무에서 계속되면 요청 ID 와 함께 센터 운영 담당에게 문의하세요. **[재시도 가능]**\n- **BAD_REQUEST** (`5010`, HTTP 400) — 요청 형식이 올바르지 않습니다(필수 값 누락, receiptId 형식, 가명 형식, 해시 불일치, channel 10자 초과, GET purpose 값 오류 등). PUT·POST 의 Content-Type 이 application/json 이 아니거나 없으면 HTTP 415 로 같은 키가 나갑니다. _조치:_ 응답의 message 를 확인하고 요청을 고쳐서 보내세요. PUT·POST 에는 Content-Type: application/json 헤더가 필요합니다.\n- **DIVISION_TYPE** (`4003`, HTTP 400) — 분할 유형이 1 또는 2 가 아닙니다. _조치:_ divisionType 을 1(연속) 또는 2(블록 교차)로 보내세요.\n- **DIVISION_RATE** (`4004`, HTTP 400) — 분할 비율이 1~9 범위가 아닙니다. _조치:_ divisionRate 를 1~9 로 보내세요(5 = 50:50).\n- **ALREADY_ENROLLED** (`5013`, HTTP 409) — 같은 receiptId 가 다른 내용으로 이미 저장되어 있습니다. _조치:_ 새 receiptId 를 만들어 보내세요. 같은 receipt 본문(SHA-256 이 같음)의 재전송은 오류가 아니라 200 으로 응답합니다.\n- **RECEIPT_NOT_FOUND** (`5014`, HTTP 404) — receipt 가 없거나 이미 파기되었거나 다른 기관 소유입니다. 확인(confirm) 전(PENDING)이거나 확인 기한이 지나 만료 파기된 receipt 도 같은 응답입니다(구분하지 않음). 확인 전 receipt 는 기관이 DELETE 로 파기할 수도 없습니다(404). _조치:_ 조회라면 고객 재등록이 필요한지 판단하세요. **confirm 이 404 면 새 receiptId 로 PUT 부터 다시** 하세요(만료·파기된 receiptId 로 PUT 하면 400, 다른 기관 소유면 409). 센터 복원 직후에는 백업 이후 등록분이 없을 수 있습니다.\n- **COMPAT_UNSUPPORTED** (`5009`, HTTP 422) — 호환 인증을 통과하지 않았거나 정지된 벤더입니다. _조치:_ GET /compat/vendors 에서 CERTIFIED 상태의 벤더를 확인하세요.\n- **OVERLOADED** (`5021`, HTTP 503) — 센터가 과부하라 **오래 붙잡지 않고 거부**했습니다(과부하 보호): 동시 처리 상한을 넘으면 즉시 또는 짧은 대기(기본 최대 0.4초) 뒤에, 내부 DB 연결 풀이 비어 있으면 연결 대기(기본 약 2.5초, 요청당 DB 대기 예산 3초) 뒤에 거부합니다. 응답에 `Retry-After`(초) 헤더가 있습니다. 요청 자체에는 문제가 없습니다. _조치:_ `Retry-After` 초 뒤에 지수 백오프(예: 1s → 2s → 4s, 최대 3~5회, 지터 포함)로 같은 요청을 재시도하세요. PUT 과 DELETE 는 멱등이라 안전하며, 재시도마다 nonce 와 서명은 새로 만드세요. 계속되면 fail-closed(인증 거부)로 처리하고 센터 운영 담당에게 요청 ID 와 함께 알리세요. **[재시도 가능]**\n- **SERVICE_UNAVAILABLE** (`5022`, HTTP 503) — 센터의 내부 저장소(DB)에 연결하지 못해 **기다리지 않고 즉시 거부**했습니다(DB 장애·연결 불가·서킷 열림). 과부하(`OVERLOADED`)와 달리 센터 쪽 의존성 문제입니다. 응답에 `Retry-After`(초) 헤더가 있고 요청은 처리되지 않았습니다. _조치:_ `Retry-After` 초 뒤에 지수 백오프로 같은 요청을 재시도하세요(PUT·DELETE 는 멱등, 재시도마다 nonce·서명은 새로). 과부하와 달리 짧은 시간에 반복해도 소용이 없으니 호출 속도를 줄이고, 계속되면 fail-closed(인증 거부)로 처리하고 센터 운영 담당에게 요청 ID 와 함께 알리세요. **[재시도 가능]**\n- **CENTER_UNAVAILABLE** (`5001`, HTTP 503) — 센터가 점검 중이거나 일시적으로 처리할 수 없습니다(5xx 포함). 센터 기동 중의 503 에는 Retry-After 가 있지만, 시험용 장애 주입(DOWN·ERROR_RATE)으로 나오는 경우에는 없고, ERROR_RATE 에서는 HTTP 500 + 이 코드(5001)로 나올 수도 있습니다. _조치:_ 지연을 두고 같은 요청을 재시도하세요. PUT 과 DELETE 는 멱등이라 안전합니다. 계속되면 fail-closed(인증 거부)로 처리하세요. **[재시도 가능]**\n- **INTEGRITY_ERROR** (`5008`, HTTP 500) — 센터 내부의 예기치 못한 오류입니다. 조회(GET)에서는 저장된 receipt 의 무결성 검증 실패를 뜻하기도 합니다(요청 형식 오류는 이 코드가 아니라 400/415 로 나갑니다). _조치:_ 재시도하지 말고 요청 ID 와 함께 센터 운영 담당에게 즉시 문의하세요.\n","version":"1.3.2"},"servers":[{"url":"/","description":"현재 접속한 서버 — 경로는 /open-api/v1/... 전체를 사용"}],"security":[{"instCode":[],"timestamp":[],"nonce":[],"signature":[]}],"tags":[{"name":"상태","description":"서명 없이 호출할 수 있는 공개 경로"},{"name":"receipt 보관","description":"분할된 얼굴 템플릿 조각(receipt)의 저장·조회·암호 파기"},{"name":"호환 인증","description":"호환 인증을 통과한 벤더 목록과 기관의 결과 보고"}],"paths":{"/open-api/v1/receipts/{receiptId}":{"get":{"tags":["receipt 보관"],"summary":"receipt 조회","description":"조회 결과(성공·없음·파기·다른 기관 소유)마다 **접근 이력**(기관·목적·결과·요청 ID)이 기록됩니다. 확인 전(PENDING) 조회(404)와 요청 오류(400)는 이력에 남지 않습니다. 없음·파기·다른 기관 소유·**확인(confirm) 전(PENDING)** 은 모두 `404 RECEIPT_NOT_FOUND` 이며 구분하지 않습니다. 응답의 `receiptSha256` 으로 무결성을 다시 확인하세요(불일치하면 사용하지 말고 요청 ID 와 함께 문의).","operationId":"getReceipt","parameters":[{"name":"receiptId","in":"path","description":"receipt 식별자","required":true,"schema":{"type":"string"},"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"},{"name":"purpose","in":"query","description":"조회 목적(접근 이력에 기록). 생략하면 VERIFY","required":false,"schema":{"type":"string","default":"VERIFY","enum":["VERIFY","ENROLL_CHECK","ADMIN"]}},{"name":"X-Request-Id","in":"header","description":"거래 ID(선택). 한 거래가 만드는 모든 호출에 같은 값을 보내면 센터의 처리 단계 기록이 한 거래로 묶이고 응답 헤더로 돌아옵니다. `[A-Za-z0-9._:-]{8,40}`","required":false,"schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060","pattern":"^[A-Za-z0-9._:-]{8,40}$"}}],"responses":{"200":{"description":"조회 성공","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"SERVED"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceiptGetResponse"}}}},"400":{"description":"receiptId 형식 오류 또는 `purpose` 값 오류(`BAD_REQUEST`)","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"REJECTED"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"$ref":"#/components/responses/AuthFailed"},"404":{"description":"receipt 없음·확인 전(PENDING)·파기됨·다른 기관 소유(`RECEIPT_NOT_FOUND`)","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"NOT_FOUND"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"code":5014,"key":"RECEIPT_NOT_FOUND","message":"receipt not found","requestId":"3f9a1c0e7b2d4a56b8c1"}}}},"409":{"$ref":"#/components/responses/ReplayDetected"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}}},"put":{"tags":["receipt 보관"],"summary":"receipt 저장(멱등) — 확인 대기(PENDING)","description":"분할된 얼굴 템플릿의 센터 보관 조각(receipt)을 저장합니다. 본문은 **봉투암호화**되어 저장되며 `receiptSha256` 이 본문과 다르면 거부됩니다.\n\n**2단계 저장**: 저장된 receipt 는 **확인 대기(PENDING)** 입니다. `POST /receipts/{id}/confirm` 으로 **확인해야 조회·인증에 쓸 수 있고**(그 전의 GET 은 404),\n확인 기한(기본 300초) 안에 확인하지 않으면 센터가 암호 파기합니다(기관의 등록 응답이 유실·타임아웃되어도 고아 receipt 가 남지 않게). 신규 저장은 `201` + 응답 헤더 `X-Center-Result: STORED`.\n같은 receipt 본문(SHA-256 이 같음)의 재전송은 확인 대기 중이면 기한만 늘리고 200(`UNCHANGED`)입니다.\n\n**멱등·재시도**: 같은 `receiptId` 로 **같은 receipt 본문(SHA-256 이 같음)** 을 다시 보내면 `200`(이미 저장됨)을 돌려주므로 타임아웃 후 안전하게 재시도할 수 있습니다(본문 외의 필드는 비교하지 않습니다).\n다른 본문이면 `409 ALREADY_ENROLLED`, 파기된 `receiptId` 는 다시 쓸 수 없습니다(`400`).\n`customerRef` 는 평문 고객 식별자가 아니라 **기관이 만든 가명(HMAC, 64자리 소문자 hex)** 이어야 하며 평문은 거부됩니다.\n\n**요청 형식**: `Content-Type: application/json` 이 필요합니다(아니면 `415 BAD_REQUEST`). 본문 `institutionCode` 는 인증된 기관과 같아야 하며(누락·불일치는 `401 AUTH_FAILED`),\n`channel` 은 최대 10자(넘으면 `400`), `receiptB64` 는 디코딩 후 1~8192바이트(Base64 패딩 생략 가능), `receiptSha256` 은 대소문자 무관(응답은 소문자), `vendorCode` 는 정확히 4자(대소문자 구분)입니다.\n`techCode`·`divisionType`·`divisionRate` 를 빠뜨리면 0 으로 처리되어 각각 `422`·`400`(4003)·`400`(4004) 로 보고됩니다.","operationId":"putReceipt","parameters":[{"name":"receiptId","in":"path","description":"기관이 UUID 로 생성하는 식별자(영문·숫자·하이픈 8~64자). 서명 대상 경로에 그대로 포함됩니다.","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9-]{8,64}$"},"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"},{"name":"X-Request-Id","in":"header","description":"거래 ID(선택). 한 거래가 만드는 모든 호출에 같은 값을 보내면 센터의 처리 단계 기록이 한 거래로 묶이고 응답 헤더로 돌아옵니다. `[A-Za-z0-9._:-]{8,40}`","required":false,"schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060","pattern":"^[A-Za-z0-9._:-]{8,40}$"}}],"requestBody":{"description":"저장할 receipt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceiptPutRequest"},"examples":{"50:50 분할 저장":{"description":"50:50 분할 저장","value":{"institutionCode":"NH001","customerRef":"9f2c4be1d7a3085c61e2f4ab93d70c58b1a64e0f7d2c93a85b6e10d4f7a2c9e3","vendorCode":"C019","techCode":105,"divisionType":1,"divisionRate":5,"receiptB64":"AAECAwQFBgcICQoLDA0ODw==","receiptSha256":"ad3d9ff4f5f2c8b0a53d5e1f2c0f6e8e1b7e43d3d5e1b0d6c6a5e2c1f1c4f9a2","channel":"APP"}}}}},"required":true},"responses":{"201":{"description":"신규 저장","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"STORED"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceiptPutResponse"},"example":{"receiptId":"6de611e1-6861-40d7-bc5f-e00d4cddd060","storedAt":"2026-10-09T06:23:42.440733Z","receiptSha256":"ad3d9ff4f5f2c8b0a53d5e1f2c0f6e8e1b7e43d3d5e1b0d6c6a5e2c1f1c4f9a2"}}}},"200":{"description":"같은 receipt 본문(SHA-256 이 같음) 재전송 — 이미 저장되어 있음(재시도 안전)","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"UNCHANGED"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceiptPutResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/AuthFailed"},"409":{"description":"같은 receiptId 가 다른 내용으로 이미 존재(`ALREADY_ENROLLED`) 또는 nonce 재사용(`REPLAY_DETECTED`)","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"REJECTED"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"code":5013,"key":"ALREADY_ENROLLED","message":"receiptId already exists with different content","requestId":"3f9a1c0e7b2d4a56b8c1"}}}},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"description":"호환 인증을 통과하지 않았거나 정지된 벤더(`COMPAT_UNSUPPORTED`)","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"REJECTED"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}}},"delete":{"tags":["receipt 보관"],"summary":"receipt 암호 파기(멱등)","description":"데이터키와 암호화 본문을 삭제(암호 파기)하고 **파기 원장**에 기록한 뒤 증적번호를 돌려줍니다.\n백업에 본문 사본이 남아 있어도 키가 없어 복호화할 수 없습니다.\n\n**멱등·재시도**: 이미 파기된 건을 다시 호출하면 **같은 증적번호**로 `200` 을 돌려줍니다. 응답을 받지 못했다면 그대로 재시도하세요.\n`reason` 은 필수입니다.\n\n**404**: 없는 receipt, 다른 기관의 receipt, 그리고 **확인(confirm) 전인 PENDING receipt**(기관이 미확인 receipt 를 직접 파기할 수 없음)입니다.\n확인 기한(기본 300초)이 지나 센터가 자동 파기(사유 `UNCONFIRMED_EXPIRED`)한 뒤에는 DELETE 가 `200` 과 그 증적번호를 돌려줍니다.","operationId":"destroyReceipt","parameters":[{"name":"receiptId","in":"path","description":"receipt 식별자","required":true,"schema":{"type":"string"},"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"},{"name":"reason","in":"query","description":"파기 사유(필수). WITHDRAW 해지 / DORMANT 장기 미사용 / DECEASED 사망 / ADMIN 관리자 처리","required":true,"schema":{"type":"string","enum":["WITHDRAW","DORMANT","DECEASED","ADMIN"]}},{"name":"X-Request-Id","in":"header","description":"거래 ID(선택). 한 거래가 만드는 모든 호출에 같은 값을 보내면 센터의 처리 단계 기록이 한 거래로 묶이고 응답 헤더로 돌아옵니다. `[A-Za-z0-9._:-]{8,40}`","required":false,"schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060","pattern":"^[A-Za-z0-9._:-]{8,40}$"}}],"responses":{"200":{"description":"파기 완료(또는 이미 파기됨)","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"DESTROYED"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceiptDestroyResponse"},"example":{"receiptId":"6de611e1-6861-40d7-bc5f-e00d4cddd060","destroyedAt":"2026-10-09T06:27:12.480317Z","certificateNo":"KFTC-D-20261009-00000006"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/AuthFailed"},"404":{"description":"receipt 없음·다른 기관 소유·확인(confirm) 전 PENDING(`RECEIPT_NOT_FOUND`)","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"NOT_FOUND"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"$ref":"#/components/responses/ReplayDetected"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/open-api/v1/receipts/{receiptId}/confirm":{"post":{"tags":["receipt 보관"],"summary":"receipt 확인(confirm) — PENDING → ACTIVE(멱등)","description":"`PUT` 으로 저장한 receipt 를 **확인**해 조회·인증에 쓸 수 있게(ACTIVE) 합니다. 본문은 없습니다(서명은 빈 본문 해시로).\n기관이 자기 쪽 등록(티켓 보관)을 마친 **뒤에** 호출하세요. 이미 확인된 receipt 는 그대로 200(**멱등**)이고,\n없음·확인 기한 만료·파기됨·다른 기관 소유는 모두 `404 RECEIPT_NOT_FOUND` 입니다(구분하지 않음). 응답은 저장 응답과 같은 DTO, 헤더 `X-Center-Result: CONFIRMED`.\n**404 를 받으면 새 receiptId 로 PUT 부터 다시** 하세요(만료·파기된 receiptId 로 PUT 하면 400, 다른 기관 소유면 409).","operationId":"confirmReceipt","parameters":[{"name":"receiptId","in":"path","description":"확인할 receipt 식별자(PUT 한 것)","required":true,"schema":{"type":"string"},"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"},{"name":"X-Request-Id","in":"header","description":"거래 ID(선택). 한 거래가 만드는 모든 호출에 같은 값을 보내면 센터의 처리 단계 기록이 한 거래로 묶이고 응답 헤더로 돌아옵니다. `[A-Za-z0-9._:-]{8,40}`","required":false,"schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060","pattern":"^[A-Za-z0-9._:-]{8,40}$"}}],"responses":{"200":{"description":"확인 완료(또는 이미 확인됨)","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"CONFIRMED"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceiptPutResponse"}}}},"400":{"description":"receiptId 형식 오류(`BAD_REQUEST`)","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"REJECTED"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"$ref":"#/components/responses/AuthFailed"},"404":{"description":"receipt 없음·확인 기한 만료·파기됨·다른 기관 소유(`RECEIPT_NOT_FOUND`)","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"NOT_FOUND"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"$ref":"#/components/responses/ReplayDetected"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/open-api/v1/compat/report":{"post":{"tags":["호환 인증"],"summary":"호환 인증 결과 보고","description":"기관이 **자체적으로 수행한** 호환 인증 결과(점수·통과 여부)만 보고합니다. 템플릿은 전송하지 않으며, 보고가 실패해도 본인 인증 결과에는 영향이 없으므로 실패 시 재시도하지 않아도 됩니다. `Content-Type: application/json` 이 필요하고(아니면 `415`), 본문 `institutionCode` 는 인증된 기관과 같아야 합니다(누락·불일치는 `401 AUTH_FAILED`).","operationId":"reportCompat","parameters":[{"name":"X-Request-Id","in":"header","description":"거래 ID(선택). 한 거래가 만드는 모든 호출에 같은 값을 보내면 센터의 처리 단계 기록이 한 거래로 묶이고 응답 헤더로 돌아옵니다. `[A-Za-z0-9._:-]{8,40}`","required":false,"schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060","pattern":"^[A-Za-z0-9._:-]{8,40}$"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompatReport"}}},"required":true},"responses":{"200":{"description":"접수","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"OK"}}},"content":{"application/json":{"example":{"accepted":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/AuthFailed"},"409":{"$ref":"#/components/responses/ReplayDetected"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"description":"등록되지 않은 벤더(`COMPAT_UNSUPPORTED`)","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"REJECTED"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/open-api/v1/health":{"get":{"tags":["상태"],"summary":"서비스 상태","description":"서명 없이 호출할 수 있는 공개 경로입니다. LB·모니터링의 생존 확인에 사용합니다. 장애 주입 `DOWN` 중에는 503 을 돌려줍니다.","operationId":"health","parameters":[{"name":"X-Request-Id","in":"header","description":"거래 ID(선택). 한 거래가 만드는 모든 호출에 같은 값을 보내면 센터의 처리 단계 기록이 한 거래로 묶이고 응답 헤더로 돌아옵니다. `[A-Za-z0-9._:-]{8,40}`","required":false,"schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060","pattern":"^[A-Za-z0-9._:-]{8,40}$"}}],"responses":{"200":{"description":"정상","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"OK"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"},"example":{"status":"UP","time":"2026-10-09T06:20:31.216747Z","version":"0.1.0","faultMode":"NONE"}}}},"503":{"$ref":"#/components/responses/Unavailable"}},"security":[]}},"/open-api/v1/compat/vendors":{"get":{"tags":["호환 인증"],"summary":"호환 인증 벤더 목록","description":"호환 인증을 통과한 벤더·규격 목록입니다. **`status` 가 `CERTIFIED` 인 벤더만** 교차 인증에 사용할 수 있고, `SUSPENDED` 는 신규 receipt 저장이 거부됩니다.","operationId":"listCompatVendors","parameters":[{"name":"X-Request-Id","in":"header","description":"거래 ID(선택). 한 거래가 만드는 모든 호출에 같은 값을 보내면 센터의 처리 단계 기록이 한 거래로 묶이고 응답 헤더로 돌아옵니다. `[A-Za-z0-9._:-]{8,40}`","required":false,"schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060","pattern":"^[A-Za-z0-9._:-]{8,40}$"}}],"responses":{"200":{"description":"목록","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"OK"}}},"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CompatVendor"}},"example":[{"vendorCode":"C019","vendorName":"시선AI 얼굴인식(C019)","techCode":105,"templateVersion":1,"status":"CERTIFIED","certifiedAt":"2026-10-08T12:54:53.653621Z"}]}}},"401":{"$ref":"#/components/responses/AuthFailed"},"409":{"$ref":"#/components/responses/ReplayDetected"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/Unavailable"}}}}},"components":{"schemas":{"ReceiptPutRequest":{"type":"object","example":{"institutionCode":"NH001","customerRef":"9f2c4be1d7a3085c61e2f4ab93d70c58b1a64e0f7d2c93a85b6e10d4f7a2c9e3","vendorCode":"C019","techCode":105,"divisionType":1,"divisionRate":5,"receiptB64":"AAECAwQFBgcICQoLDA0ODw==","receiptSha256":"ad3d9ff4f5f2c8b0a53d5e1f2c0f6e8e1b7e43d3d5e1b0d6c6a5e2c1f1c4f9a2","channel":"APP"},"properties":{"institutionCode":{"type":"string","description":"인증된 기관 코드와 같아야 합니다(누락·불일치는 401 AUTH_FAILED)","example":"NH001"},"customerRef":{"type":"string","description":"고객 식별자의 가명(HMAC, 64자리 소문자 hex). 평문 식별자는 거부됩니다","example":"9f2c4be1d7a3085c61e2f4ab93d70c58b1a64e0f7d2c93a85b6e10d4f7a2c9e3"},"vendorCode":{"type":"string","description":"업체 코드 정확히 4자(대소문자 구분). 호환 인증을 통과(CERTIFIED)한 벤더만 가능","example":"C019"},"techCode":{"type":"integer","format":"int32","description":"기술 코드(얼굴 = 105). 누락하면 0 으로 처리되어 422 COMPAT_UNSUPPORTED","example":105},"divisionType":{"type":"integer","format":"int32","description":"분할 유형. 1 = 연속 분할, 2 = 10바이트 블록 교차. 누락하면 0 으로 처리되어 400 DIVISION_TYPE(4003)","enum":[1,2],"example":1},"divisionRate":{"type":"integer","format":"int32","description":"ticket(금융기관 보관 조각)이 가져간 비율(1/10 단위, 1~9). 5 = 50:50. 누락하면 0 으로 처리되어 400 DIVISION_RATE(4004)","example":5,"maximum":9,"minimum":1},"receiptB64":{"type":"string","description":"센터에 맡길 receipt 조각(표준 Base64, 패딩 `=` 생략 가능). 디코딩 후 1~8192바이트","example":"AAECAwQFBgcICQoLDA0ODw=="},"receiptSha256":{"type":"string","description":"디코딩한 receipt 바이트의 SHA-256(hex, 대소문자 무관 — 응답은 항상 소문자). 본문과 다르면 거부","example":"ad3d9ff4f5f2c8b0a53d5e1f2c0f6e8e1b7e43d3d5e1b0d6c6a5e2c1f1c4f9a2"},"channel":{"type":"string","description":"등록 채널(선택, 최대 10자 — 넘으면 400 BAD_REQUEST)","example":"APP"}},"required":["customerRef","divisionRate","divisionType","institutionCode","receiptB64","receiptSha256","techCode","vendorCode"]},"ReceiptPutResponse":{"type":"object","example":{"receiptId":"6de611e1-6861-40d7-bc5f-e00d4cddd060","storedAt":"2026-10-09T06:23:42.440733Z","receiptSha256":"ad3d9ff4f5f2c8b0a53d5e1f2c0f6e8e1b7e43d3d5e1b0d6c6a5e2c1f1c4f9a2"},"properties":{"receiptId":{"type":"string","description":"저장(또는 확인)된 receipt 식별자","example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"},"storedAt":{"type":"string","format":"date-time","description":"저장 시각(UTC, RFC 3339 — 소수초 자릿수 가변). 같은 receipt 본문(SHA-256 이 같음) 재전송이면 최초 저장 시각","example":"2026-10-09T06:23:42.440733Z"},"receiptSha256":{"type":"string","description":"센터가 계산한 본문 해시","example":"ad3d9ff4f5f2c8b0a53d5e1f2c0f6e8e1b7e43d3d5e1b0d6c6a5e2c1f1c4f9a2"}}},"ApiError":{"type":"object","example":{"code":5014,"key":"RECEIPT_NOT_FOUND","message":"receipt not found","requestId":"3f9a1c0e7b2d4a56b8c1"},"properties":{"code":{"type":"integer","format":"int32","description":"오류 번호(4xxx 는 KFTC 벤더 라이브러리와 동일, 5xxx 는 본 시스템 고유)","example":5014},"key":{"type":"string","description":"오류 이름","example":"RECEIPT_NOT_FOUND"},"message":{"type":"string","description":"사람이 읽을 수 있는 설명","example":"receipt not found"},"requestId":{"type":"string","description":"문의 시 알려 주세요. 응답 헤더 X-Request-Id 와 같습니다","example":"3f9a1c0e7b2d4a56b8c1"}}},"CompatReport":{"type":"object","example":{"institutionCode":"NH001","enrolledVendor":"V002","probeVendor":"C019","score":91.2,"pass":true,"requestId":"tx-20261002-0001"},"properties":{"institutionCode":{"type":"string","description":"보고하는 기관 코드(인증된 기관과 같아야 함 — 누락·불일치는 401 AUTH_FAILED)","example":"NH001"},"enrolledVendor":{"type":"string","description":"등록 시점 벤더","example":"V002"},"probeVendor":{"type":"string","description":"인증 시점 벤더","example":"C019"},"score":{"type":"number","format":"double","description":"기관이 로컬에서 비교한 점수(0~100)","example":91.2,"maximum":100,"minimum":0},"pass":{"type":"boolean","description":"기준점수 통과 여부","example":true},"requestId":{"type":"string","description":"기관 쪽 거래 식별자(선택)","example":"tx-20261002-0001"}},"required":["enrolledVendor","institutionCode","pass","probeVendor","score"]},"ReceiptGetResponse":{"type":"object","example":{"receiptId":"6de611e1-6861-40d7-bc5f-e00d4cddd060","receiptB64":"AAECAwQFBgcICQoLDA0ODw==","vendorCode":"C019","techCode":105,"divisionType":1,"divisionRate":5,"receiptSha256":"ad3d9ff4f5f2c8b0a53d5e1f2c0f6e8e1b7e43d3d5e1b0d6c6a5e2c1f1c4f9a2"},"properties":{"receiptId":{"type":"string","description":"receipt 식별자","example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"},"receiptB64":{"type":"string","description":"복호화한 receipt 조각(표준 Base64)","example":"AAECAwQFBgcICQoLDA0ODw=="},"vendorCode":{"type":"string","description":"저장 시 벤더","example":"C019"},"techCode":{"type":"integer","format":"int32","description":"기술 코드","example":105},"divisionType":{"type":"integer","format":"int32","description":"분할 유형","example":1},"divisionRate":{"type":"integer","format":"int32","description":"분할 비율","example":5},"receiptSha256":{"type":"string","description":"본문 해시 — 받은 본문의 SHA-256 과 대조해 무결성을 확인","example":"ad3d9ff4f5f2c8b0a53d5e1f2c0f6e8e1b7e43d3d5e1b0d6c6a5e2c1f1c4f9a2"}}},"Health":{"type":"object","example":{"status":"UP","time":"2026-10-09T06:20:31.216747Z","version":"0.1.0","faultMode":"NONE"},"properties":{"status":{"type":"string","description":"UP","example":"UP"},"time":{"type":"string","format":"date-time","description":"서버 시각(UTC, RFC 3339 — 소수초 자릿수 가변)","example":"2026-10-09T06:20:31.216747Z"},"version":{"type":"string","description":"센터 버전","example":"0.1.0"},"faultMode":{"type":"string","description":"장애 주입 모드(NONE / LATENCY / ERROR_RATE / DOWN)","example":"NONE"}}},"CompatVendor":{"type":"object","example":{"vendorCode":"C019","vendorName":"시선AI 얼굴인식(C019)","techCode":105,"templateVersion":1,"status":"CERTIFIED","certifiedAt":"2026-10-08T12:54:53.653621Z"},"properties":{"vendorCode":{"type":"string","description":"업체 코드","example":"C019"},"vendorName":{"type":"string","description":"업체(솔루션) 이름","example":"시선AI 얼굴인식(C019)"},"techCode":{"type":"integer","format":"int32","description":"기술 코드","example":105},"templateVersion":{"type":"integer","format":"int32","description":"템플릿 규격 버전","example":1},"status":{"type":"string","description":"CERTIFIED(호환 인증 통과) 또는 SUSPENDED(정지)","example":"CERTIFIED"},"certifiedAt":{"type":"string","format":"date-time","description":"호환 인증 일시(UTC, RFC 3339 — 소수초 자릿수 가변)","example":"2026-10-08T12:54:53.653621Z"}}},"ReceiptDestroyResponse":{"type":"object","example":{"receiptId":"6de611e1-6861-40d7-bc5f-e00d4cddd060","destroyedAt":"2026-10-09T06:27:12.480317Z","certificateNo":"KFTC-D-20261009-00000006"},"properties":{"receiptId":{"type":"string","description":"파기한 receipt 식별자","example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"},"destroyedAt":{"type":"string","format":"date-time","description":"파기 시각(UTC, RFC 3339 — 소수초 자릿수 가변). 이미 파기된 건이면 최초 파기 시각","example":"2026-10-09T06:27:12.480317Z"},"certificateNo":{"type":"string","description":"파기 증적번호. 같은 건을 다시 호출해도 동일","example":"KFTC-D-20261009-00000006"}}}},"responses":{"BadRequest":{"description":"요청 형식 오류(`BAD_REQUEST`, `DIVISION_TYPE`, `DIVISION_RATE` 등). message 를 확인하고 요청을 고쳐서 보내세요","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"REJECTED"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"AuthFailed":{"description":"인증 실패(`AUTH_FAILED`). 보안상 사유를 구분하지 않습니다 — 키 불일치·시각 오차(±60초 초과)·허용 IP 밖·정지·인증 헤더 누락·`X-Nonce` 형식 위반·`X-Timestamp` 비숫자/밀리초·요청 본문 1 MiB 초과(413 아님)·PUT·compat/report 본문 `institutionCode` 누락/불일치","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"REJECTED"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"key":"AUTH_FAILED","code":5011,"requestId":"3f9a1c0e7b2d4a56b8c1","message":"authentication failed"}}}},"ReplayDetected":{"description":"nonce 재사용(`REPLAY_DETECTED`). 요청마다 새 nonce 를 만들고 다시 서명해서 보내세요","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"REJECTED"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"key":"REPLAY_DETECTED","code":5004,"requestId":"3f9a1c0e7b2d4a56b8c1","message":"request rejected"}}}},"UnsupportedMediaType":{"description":"본문의 `Content-Type` 이 `application/json` 이 아니거나 없음(`BAD_REQUEST`, HTTP 415). PUT·POST 는 `Content-Type: application/json` 이 필요합니다","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"REJECTED"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"key":"BAD_REQUEST","code":5010,"requestId":"3f9a1c0e7b2d4a56b8c1","message":"unsupported media type: Content-Type must be application/json"}}}},"RateLimited":{"description":"기관별 분당 호출 한도 초과 또는 이상 접근 차단(`RATE_LIMITED`). `Retry-After` 는 없습니다(이상 접근 차단은 기본 300초 이어질 수 있고 남은 시간을 알 수 없음). 지수 백오프로 간격을 늘려 재시도하세요","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"REJECTED"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"InternalError":{"description":"서버 내부의 예기치 못한 오류(`INTEGRITY_ERROR`, 5008). 조회(GET)에서는 저장된 receipt 의 무결성 검증 실패를 뜻하기도 합니다. 재시도하지 말고 요청 ID 와 함께 문의하세요","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"REJECTED"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"key":"INTEGRITY_ERROR","code":5008,"requestId":"3f9a1c0e7b2d4a56b8c1","message":"internal error"}}}},"Unavailable":{"description":"센터 점검·장애(`CENTER_UNAVAILABLE`), 내부 저장소(DB) 연결 불가(`SERVICE_UNAVAILABLE`) 또는 과부하 보호(`OVERLOADED`). 응답 헤더 `Retry-After` 는 `OVERLOADED`·`SERVICE_UNAVAILABLE`·센터 기동 중 503 에만 있고 시험용 장애 주입의 `CENTER_UNAVAILABLE` 에는 없습니다. 있으면 그 시간(초) 뒤에, 없으면 지수 백오프로 같은 요청을 재시도하세요(PUT·DELETE 는 멱등)","headers":{"X-Request-Id":{"description":"거래 ID 에코(요청에 보낸 X-Request-Id, 없으면 센터가 만든 값)","schema":{"example":"6de611e1-6861-40d7-bc5f-e00d4cddd060"}},"X-Center-Node":{"description":"요청을 처리한 센터 노드","schema":{"example":"kftc-a"}},"X-Process-Ms":{"description":"센터 내부 처리 시간(ms, 올림 정수)","schema":{"example":"12"}},"X-Center-Result":{"description":"처리 결과 코드: STORED·UNCHANGED·CONFIRMED·SERVED·DESTROYED·NOT_FOUND·REJECTED(그 밖의 성공 응답은 OK). 오류 응답은 receipt 없음이면 NOT_FOUND, 그 밖에는 REJECTED(프레임워크 기본 404/405/406 응답은 예외로 OK)","schema":{"example":"REJECTED"}},"Retry-After":{"description":"재시도까지 기다릴 초. 503 OVERLOADED·SERVICE_UNAVAILABLE·센터 기동 중 503 에만 있음(장애 주입의 CENTER_UNAVAILABLE 과 429 에는 없음)","schema":{"example":"1"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"securitySchemes":{"instCode":{"type":"apiKey","description":"기관 코드(예: NH001)","name":"X-Inst-Code","in":"header"},"timestamp":{"type":"apiKey","description":"epoch 초(밀리초·소수 불가). 센터 시각과 ±60초(경계 포함) 이내","name":"X-Timestamp","in":"header"},"nonce":{"type":"apiKey","description":"요청마다 새로 만드는 값 `[A-Za-z0-9_.:-]{8,80}`. 기관별로 약 5분 동안 기억하며 한 번만 사용 가능","name":"X-Nonce","in":"header"},"signature":{"type":"apiKey","description":"HMAC-SHA256 서명(소문자 hex). 서명 생성기(/portal/docs#signer)로 계산","name":"X-Signature","in":"header"}}}}