바이오 분산관리 센터 — 개발자 문서금융결제원(모의)

개발자 문서 — 센터 Open API

연계기관 개발자를 위한 API 가이드입니다. 같은 내용이 Swagger UI와 OpenAPI 명세(JSON)로도 제공됩니다. 모든 예시는 가상의 값입니다.
이 문서는 포털 계정 없이 열람할 수 있도록 공개되어 있습니다(poc.docs.public=true). 문서에는 어떤 인증키·비밀값도 들어 있지 않지만, 운영 환경에서는 비공개(poc.docs.public=false, 포털 로그인 필요)를 권장합니다.

개요

  • Base 경로 /open-api/v1 · 형식 JSON(UTF-8) · 시각은 UTC ISO-8601 입니다. 서명하는 경로는 /open-api/v1/... 전체입니다.
  • 기관은 분할한 얼굴 템플릿의 센터 보관 조각(receipt)을 저장·조회·암호 파기하고, 호환 인증을 통과한 벤더 목록을 조회합니다.
  • 운영 구성에서는 Open API 가 연계 전용 mTLS 포트(기본 9143)에서만 열립니다. 개발용 plain 구성에서는 포털과 같은 포트(9150)입니다.
  • 고객 식별자는 평문이 아니라 기관이 만든 가명(HMAC-SHA256, 64자리 소문자 hex)으로만 보냅니다. 센터는 receipt 를 실제 고객과 연결할 수 없습니다.

인증(3중)

  1. mTLS — 클라이언트 인증서 필수(TLS 1.3). 인증서 CN 은 기관에 등록된 값과 같아야 합니다.
  2. 요청 서명 — 헤더 4개를 보냅니다.
    헤더값
    X-Inst-Code기관 코드(예: NH001)
    X-Timestampepoch 초(밀리초·소수 불가). 센터 시각과 ±60초(경계 포함) 이내
    X-Nonce요청마다 새로 만드는 값 [A-Za-z0-9_.:-]{8,80}. 기관별로 약 5분 동안 기억하며 한 번만 사용 가능
    X-Signature아래 규칙의 HMAC-SHA256, 소문자 hex
    canonical = METHOD + "\n" + 경로?쿼리 + "\n" + X-Timestamp + "\n" + X-Nonce + "\n" + SHA-256(본문 바이트, 소문자 hex)
    X-Signature = HMAC-SHA256(인증키, canonical) 의 소문자 hex

    본문이 없으면 빈 바이트열의 SHA-256(e3b0c442…b855)을 씁니다. 경로·쿼리는 실제로 보내는 문자열 그대로 서명해야 합니다(쿼리 순서·인코딩 포함).

  3. 재전송 방지 — 시각 오차 ±60초(경계 포함, 그래서 서명은 최대 약 2분 통과 가능), nonce 는 약 5분 동안 기억하며 재사용 거부(REPLAY_DETECTED).

허용 IP 가 등록된 기관은 허용 대역 밖 호출이 거부됩니다. 허용 IP 는 센터가 보는 접속 주소 기준입니다: 로드밸런서(LB)가 HTTP 모드로 요청을 전달하면 센터는 신뢰하도록 설정된 프록시가 붙인 X-Forwarded-For 의 원 클라이언트 주소로 판정하지만, mTLS 를 L4(TCP) 로 그대로 통과(passthrough)시키는 구성에서는 센터가 원 IP 를 볼 수 없으므로 IP 허용 목록은 LB·방화벽 ACL 에서 강제해야 합니다(센터에서는 LB 주소로만 보임). 인증 실패의 사유(키·시각·허용 IP·정지·인증서 CN)는 보안상 구분해서 알려 주지 않으며 모두 AUTH_FAILED 입니다.

인증키 교체: 운영팀이 인증키를 재발급하면 이전 키가 유예 시간(기본 10분) 동안 함께 허용됩니다. 그 안에 새 키로 바꾸세요.

서명 생성기

브라우저 안에서만 계산합니다. 입력한 인증키와 본문은 서버로 전송되지 않으며 화면을 닫으면 사라집니다. 테스트용으로만 쓰고, 운영 인증키는 입력하지 마세요.

거래 ID 와 응답 헤더

기관의 한 거래(등록·인증·파기)가 센터 호출을 여러 번 만들 수 있으므로, 거래마다 만든 ID(UUID 권장)를 X-Request-Id 요청 헤더로 보내세요. 센터는 이 값을 처리 단계 기록(수신 → 인증 → 처리 → 응답)의 상관키로 쓰고 응답 헤더로 그대로 돌려줍니다. 서명 대상에는 포함되지 않습니다. 형식은 [A-Za-z0-9._:-]{8,40}, 없거나 맞지 않으면 센터가 만듭니다.

응답 헤더의미
X-Request-Id거래 ID 에코. 오류 본문의 requestId 와 같은 값입니다.
X-Center-Node요청을 처리한 센터 노드(예: kftc-a)
X-Process-Ms센터 내부 처리 시간(밀리초, 올림 정수)
X-Center-ResultSTORED(신규 저장, 확인 대기) · UNCHANGED(동일 내용 재전송) · CONFIRMED(확인 완료·멱등 재확인) · SERVED(조회 제공) · DESTROYED(암호 파기) · NOT_FOUND(receipt 없음·파기·타 기관) · REJECTED(인증 실패·재전송·한도·검증 실패 등 모든 거부) · OK(그 밖의 성공: 호환 벤더 조회·결과 보고·상태 확인)

응답 본문(DTO)은 바뀌지 않았습니다. 인증 실패의 구체적 사유는 호출자에게 알려 주지 않으며(센터 운영자 화면에서만 확인), X-Center-Result 도 거부는 REJECTED 하나로만 내려갑니다.

등록은 2단계: 저장(PUT) → 확인(confirm)

기관이 등록 도중 센터 응답을 못 받아(타임아웃) 실패 처리해도 센터에 receipt 가 남아 소유 매핑 없이 보관되는 일(고아 receipt)을 막기 위해 2단계 저장을 씁니다.

기관                                              센터
 │  PUT  /receipts/{id}  (receipt 본문)           │
 │ ─────────────────────────────────────────────▶ │  receipt 를 PENDING 으로 암호화 저장, 확인 기한 = 지금 + 300초
 │ ◀──────────────────────────────── 201 STORED ─ │  (이때 GET 은 404 — 아직 조회·인증에 쓸 수 없음)
 │  (기관 쪽 등록 마무리: 티켓·소유 매핑 저장)      │
 │  POST /receipts/{id}/confirm  (본문 없음)       │
 │ ─────────────────────────────────────────────▶ │  PENDING → ACTIVE
 │ ◀────────────────────────────── 200 CONFIRMED ─ │  이제 GET 조회·인증 가능
 │                                                 │
 │  (confirm 을 못 보냄/실패 → 기한 경과)           │  센터가 자동으로 암호 파기(키·본문 삭제) + 파기 원장 UNCONFIRMED_EXPIRED
  • 기관은 자기 쪽 등록(티켓 저장·소유 매핑)을 마친 뒤에 confirm 을 호출하세요. 그러면 센터에는 기관이 아는 receipt 만 ACTIVE 로 남습니다.
  • PUT 응답: 201(신규)·헤더 X-Center-Result: STORED. 확인 대기 중 같은 receipt 본문(SHA-256 이 같음)을 다시 PUT 하면 확인 기한을 다시 300초로 늘리고 200(UNCHANGED), 다른 내용이면 409.
  • confirm: 멱등(이미 ACTIVE 면 200). 응답 DTO 는 PUT 과 같고(receiptId, storedAt, receiptSha256) 헤더는 X-Center-Result: CONFIRMED. 없음·확인 기한 만료·이미 파기됨·다른 기관 소유는 모두 404 RECEIPT_NOT_FOUND(구분하지 않음) — 404 면 새 receiptId 로 PUT 부터 다시 하세요(만료 파기된 receiptId 는 재사용할 수 없음).
  • 확인 전 receipt 는 GET·소유 확인에서 없음(404)으로 취급되고 접근 이력에도 남지 않습니다. DELETE 도 확인 대기 receipt 는 404 입니다(기한이 지나면 센터가 정리).
  • 확인 기한(poc.receipt.confirm-timeout-seconds, 기본 300초)을 지난 PENDING 은 센터가 30초 주기로 정리하므로 기한은 정확히 300초가 아니라 "최대 300초 + 정리 주기" 입니다. confirm 은 기한이 지난 뒤에는 정리 전이라도 404 입니다.
  • 센터 운영 설정 poc.receipt.require-confirm=false 는 구 클라이언트 호환 모드(PUT 즉시 ACTIVE)이며 이때도 confirm 은 200 으로 안전하게 호출됩니다.

엔드포인트

방식경로설명
PUT/open-api/v1/receipts/{receiptId}receipt 저장(멱등) — 확인 대기(PENDING) 로 저장. 201 신규 / 200 동일 내용 재전송
POST/open-api/v1/receipts/{receiptId}/confirmreceipt 확인(confirm) — PENDING → ACTIVE(멱등). 본문 없음. 확인해야 조회·인증에 쓸 수 있음
GET/open-api/v1/receipts/{receiptId}?purpose=VERIFYreceipt 조회. 조회마다 접근 이력이 남음. 확인(confirm) 전(PENDING)은 404
DELETE/open-api/v1/receipts/{receiptId}?reason=WITHDRAWreceipt 암호 파기(멱등). reason 필수, 같은 증적번호 반환
GET/open-api/v1/compat/vendors호환 인증 벤더 목록(CERTIFIED만 사용 가능)
POST/open-api/v1/compat/report기관이 수행한 호환 인증 결과(점수·통과 여부)만 보고
GET/open-api/v1/health서명 불필요. 상태 확인
PUT 요청·응답 예시
PUT /open-api/v1/receipts/6de611e1-6861-40d7-bc5f-e00d4cddd060
{
  "institutionCode": "NH001",
  "customerRef": "9f2c4be1d7a3085c61e2f4ab93d70c58b1a64e0f7d2c93a85b6e10d4f7a2c9e3",
  "vendorCode": "C019", "techCode": 105,
  "divisionType": 1, "divisionRate": 5,
  "receiptB64": "AAECAwQFBgcICQoLDA0ODw==",
  "receiptSha256": "ad3d9ff4f5f2c8b0a53d5e1f2c0f6e8e1b7e43d3d5e1b0d6c6a5e2c1f1c4f9a2",
  "channel": "APP"
}

201 Created
{ "receiptId": "6de611e1-…-e00d4cddd060", "storedAt": "2026-10-02T09:03:50.120Z", "receiptSha256": "ad3d9ff4…" }
GET·DELETE 응답 예시
GET /open-api/v1/receipts/6de611e1-…?purpose=VERIFY   → 200
{ "receiptId": "6de611e1-…", "receiptB64": "AAECAwQF…", "vendorCode": "C019", "techCode": 105,
  "divisionType": 1, "divisionRate": 5, "receiptSha256": "ad3d9ff4…" }

DELETE /open-api/v1/receipts/6de611e1-…?reason=WITHDRAW   → 200
{ "receiptId": "6de611e1-…", "destroyedAt": "2026-10-02T09:07:12.480Z", "certificateNo": "KFTC-D-20261002-00000006" }

오류(공통 형식)   → 404
{ "code": 5014, "key": "RECEIPT_NOT_FOUND", "message": "receipt not found", "requestId": "3f9a1c0e7b2d4a56b8c1" }

재시도·멱등 규칙

  • PUT·DELETE 는 멱등입니다. 타임아웃이나 5xx 뒤에는 같은 요청을 그대로 다시 보내도 안전합니다. 단, 재시도할 때마다 nonce 와 서명은 새로 만들어야 합니다.
  • PUT 에 같은 receiptId·같은 receipt 본문(SHA-256 이 같음)을 다시 보내면 200(확인 대기 중이면 확인 기한도 갱신), 다른 본문이면 409 ALREADY_ENROLLED, 파기된 receiptId 는 재사용할 수 없습니다.
  • confirm 도 멱등입니다(이미 ACTIVE 면 200). 타임아웃·5xx 면 같은 confirm 을 다시 보내세요. confirm 이 404 면 receipt 가 만료 파기되었을 수 있으니 새 receiptId 로 PUT 부터 다시 하세요. PUT 응답을 못 받아도 확인 기한(기본 300초) 안에 같은 receipt 본문으로 PUT 을 재전송해 응답을 받고 confirm 하거나, 그냥 두면 센터가 자동으로 파기합니다(고아 receipt 가 남지 않음).
  • DELETE 를 이미 파기된 건에 다시 보내면 같은 증적번호로 200 입니다.
  • 429·503 은 지수 백오프(예: 1s → 2s → 4s, 최대 3~5회)로 재시도합니다. 503 OVERLOADED 는 센터가 과부하라 즉시 또는 짧은 대기(기본 최대 0.4초) 뒤에 거부한 것으로 응답의 Retry-After(초) 뒤에 재시도하세요(요청은 처리되지 않았습니다). 503 SERVICE_UNAVAILABLE(5022)는 센터의 내부 저장소(DB) 장애로 즉시 거부한 것으로 마찬가지로 Retry-After 뒤에 재시도합니다(요청은 처리되지 않았습니다). 호출 한도 초과 외에도 센터가 평소와 다른 조회 패턴(짧은 시간의 대량·반복 조회, 없는 receipt 연속 조회)을 감지하면 그 기관의 receipt 조회(GET)만 일정 시간 429 로 거부될 수 있고(저장·파기·상태 확인은 영향 없음, 사유는 알려 주지 않음), 정상적인 인증 흐름(고객 1명당 receipt 1건 조회)에서는 일어나지 않습니다. 센터가 계속 응답하지 않으면 인증을 거부하는 쪽(fail-closed)으로 처리하세요. Retry-After 는 503 OVERLOADED·SERVICE_UNAVAILABLE·기동 중 503 에만 있고 429 에는 없습니다(이상 접근 차단은 기본 300초 이어질 수 있고 남은 시간을 알 수 없으니 지수 백오프로 간격을 늘리세요).
  • 400·401·404·409·422 는 재시도해도 같은 결과입니다. 요청을 고치거나 업무 처리를 하세요.
  • 센터 복구(백업 복원) 직후에는 백업 이후 등록분이 없을 수 있어 RECEIPT_NOT_FOUND 가 나오면 고객 재등록이 필요할 수 있습니다.
  • 문의할 때는 응답의 요청 ID(requestId 또는 X-Request-Id 헤더)를 알려 주세요.

오류 코드

필터·컨트롤러가 만드는 오류는 {code, key, message, requestId} 형식입니다(PUT·POST 에 Content-Type: application/json 이 아닌 본문을 보내면 415 + BAD_REQUEST). 존재하지 않는 경로(404)·허용되지 않는 메서드(405)·Accept 불일치(406)는 프레임워크 기본 응답({timestamp, status, error, path} 또는 빈 본문·HTML)이며 X-Center-Result: OK 가 붙습니다. 아래 표는 코드에서 직접 생성한 값입니다.

불러오는 중…

Swagger UI 에서 호출해 보기

Swagger UI는 명세를 읽고 요청 형식을 확인하는 용도로 쓰면 되고, Try it out 으로 실제 호출도 해 볼 수 있습니다(개발용 plain 구성). 요청마다 서명이 달라지므로 다음 순서를 따르세요.

  1. 이 페이지의 서명 생성기에 호출할 방식·경로(쿼리 포함)·본문을 Swagger 에서 보낼 것과 똑같이 입력하고 서명 계산을 누릅니다.
  2. Swagger UI 의 Authorize 에 X-Inst-Code, X-Timestamp, X-Nonce, X-Signature 네 값을 붙여 넣습니다.
  3. 같은 경로·본문으로 Try it out → Execute 합니다.
  4. 서명은 X-Timestamp 가 센터 시각 ±60초 안일 때만 유효하고(서명 후 최대 약 2분), nonce 는 한 번만 쓸 수 있습니다. 다시 호출하려면 타임스탬프·nonce·서명을 새로 만드세요.

운영 구성(secure 프로파일)에서는 Open API 가 mTLS 포트에서만 열려 브라우저의 Swagger UI 로는 호출할 수 없으므로 명세 열람용으로 쓰고, 실제 호출은 클라이언트 인증서를 가진 프로그램에서 하세요.

예제 코드

#!/usr/bin/env bash
# 사용법: ./call.sh GET "/open-api/v1/compat/vendors"
#         ./call.sh PUT "/open-api/v1/receipts/6de611e1-6861-40d7-bc5f-e00d4cddd060" '{"institutionCode":"NH001", ...}'
set -euo pipefail
BASE="http://localhost:9150"      # 운영: https://센터주소:9143 (mTLS)
INST="NH001"
KEY="<발급받은 인증키>"
METHOD="$1"; PATHQ="$2"; BODY="${3:-}"
TS="$(date +%s)"
NONCE="$(uuidgen | tr 'A-Z' 'a-z')"
BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | sed 's/^.* //')"
SIG="$(printf '%s\n%s\n%s\n%s\n%s' "$METHOD" "$PATHQ" "$TS" "$NONCE" "$BODY_HASH" \
      | openssl dgst -sha256 -hmac "$KEY" -hex | sed 's/^.* //')"
curl -sS -X "$METHOD" "$BASE$PATHQ" \
  -H "Content-Type: application/json" \
  -H "X-Inst-Code: $INST" -H "X-Timestamp: $TS" -H "X-Nonce: $NONCE" -H "X-Signature: $SIG" \
  ${BODY:+--data "$BODY"}
# mTLS 포트에서는 --cert client.pem --key client.key --cacert ca.pem 을 추가하세요.

변경 이력

버전일자내용
1.3.22026-10-09코드·실서버 교차 점검 반영: ① PUT·POST 의 Content-Type 이 application/json 이 아니면 415(BAD_REQUEST)로 답합니다(예전 500 INTEGRITY_ERROR). ② channel 이 10자를 넘으면 400(예전 500). ③ POST /compat/report 의 institutionCode 가 인증된 기관과 다르거나 없으면 401 AUTH_FAILED(예전 200 — 문서에 "같아야 함"이라고 적혀 있었으나 검증하지 않았음). ④ 명세 정정: 오류 응답 표(409·429·415·500 추가, GET·confirm 의 400), 확인 전(PENDING) receipt 의 DELETE 는 404, X-Center-Result 의 CONFIRMED, 서명 시각 ±60초(경계 포함)·nonce 약 5분, 본문 1MiB 초과는 401, 404·405·406 은 프레임워크 기본 응답.
1.3.12026-10-03센터 내부 저장소(DB) 장애 구분: DB 연결 불가·서킷 열림은 503 SERVICE_UNAVAILABLE(5022) + Retry-After 로 즉시 거부(이전에는 일부가 OVERLOADED·CENTER_UNAVAILABLE 로 나가고 요청 하나가 약 10초 걸릴 수 있었음) · 연결 풀 고갈만 OVERLOADED(5021) · 요청·응답 본문(DTO)·다른 헤더는 불변, 오류 키 SERVICE_UNAVAILABLE 한 가지 추가 · 과부하 보호 기본 상한을 서블릿 스레드 수의 3/4 로(스레드가 먼저 바닥나 연결 거부가 되는 일 방지)하고, 상한에 닿아도 최대 0.4초 대기열을 거친 뒤에(대기열이 가득 차면 즉시) 거부 — 응답 형식·헤더·오류 키는 그대로
1.3.02026-10-032단계 저장: PUT /receipts/{id} 는 receipt 를 확인 대기(PENDING)로 저장하고, 신규 POST /receipts/{id}/confirm(본문 없음, 멱등, 헤더 X-Center-Result: CONFIRMED)으로 확인해야 ACTIVE 가 됨 · 확인 전 GET 은 404 · 확인 기한(기본 300초) 안에 확인하지 않으면 센터가 암호 파기 · 요청·응답 본문(DTO)은 불변 · 센터 설정 poc.receipt.require-confirm=false 로 구 클라이언트 호환 · 허용 IP 는 센터가 보는 접속 주소 기준(로드밸런서·mTLS 통과 구성 주의)
1.2.12026-10-03모든 인증 실패(AUTH_FAILED)의 본문·메시지·상태·헤더를 사유와 무관하게 동일하게(authentication failed) · 재전송(409)·한도 초과(429)의 메시지를 request rejected 로 일반화(코드는 유지) · 비정상 접근 탐지 임계값을 기관별 호출 한도에 비례하게 · 과부하 시 503 OVERLOADED(5021) + Retry-After 로 즉시 거부(재시도 지침은 재시도·멱등 규칙 참고)
1.2.02026-10-02거래 ID(X-Request-Id) 요청 헤더 지원 · 모든 응답에 X-Request-Id·X-Center-Node·X-Process-Ms·X-Center-Result 헤더 추가(본문 변경 없음) · 평소와 다른 조회 패턴 감지 시 해당 기관의 receipt 조회가 일정 시간 429 RATE_LIMITED 로 거부될 수 있음
1.1.02026-10-02Swagger UI·OpenAPI 명세 제공 · 기관별 허용 IP · 인증키 재발급 시 이전 키 유예 허용 · 호환 벤더 상태를 CERTIFIED/SUSPENDED 로 표기 · DELETE 의 reason 필수 명시 · 오류 응답에 요청 ID 일관 제공
1.0.02026-10-02최초 공개 — receipt 저장·조회·파기, 호환 벤더 조회·결과 보고, 상태 확인. 3중 인증(mTLS·요청 서명·재전송 방지)