API 레퍼런스

기본 URL: https://trendy.storydot.kr/superapp/api · 인증: X-Client-Id + X-API-Key 헤더(인증 참고) · 모든 응답은 { "success", "data", "message" } 표준 봉투(envelope)로 감쌉니다.

주체 식별자는 항상 파트너 전용 익명 ID(pairwiseSub)입니다. 내부 식별자(civilId/userId)는 이 API 표면에 절대 나타나지 않습니다.

파트너(FI) 엔드포인트

POST/fi/score/query — 단건 점수 조회

ACTIVE 동의가 있는 주체의 점수·등급·사유코드를 반환합니다(소프트 풀). 동의가 없으면 거부됩니다.

요청

{
  "pairwiseSub": "b41c9d2f-8e77-4a10-9c3e-example",
  "purpose": "UNDERWRITING"
}

purpose 예: UNDERWRITING / ACCOUNT_REVIEW / PREQUALIFY

응답 200

{
  "success": true,
  "data": {
    "pairwiseSub": "b41c9d2f-8e77-4a10-9c3e-example",
    "score": 712,
    "scoreMin": 0,
    "scoreMax": 850,
    "tier": "GOOD",
    "pullType": "SOFT",
    "reasonCodes": [
      {
        "code": "RC_LOW_UTILITY_PAYMENT_RELIABILITY",
        "labelEn": "Low utility bill payment reliability",
        "labelLocalized": "전기·수도 등 공과금을 제때 낸 기록이 부족함",
        "category": "PAYMENT",
        "impactPoints": 38,
        "rank": 1
      },
      {
        "code": "RC_DID_VC_WEAK",
        "labelEn": "Limited verified digital identity",
        "labelLocalized": "디지털 신분증(DID)과 인증서 기록이 적음",
        "category": "IDENTITY",
        "impactPoints": 21,
        "rank": 2
      }
    ],
    "modelVersion": "scorecard-v2.1",
    "scoredAt": "2026-08-20 14:32:11",
    "decisionHash": "9f2ab1c4e5d6...64hex",
    "environment": "LIVE",
    "confidence": "HIGH",
    "dataSufficiency": {
      "featuresObserved": 18,
      "featuresTotal": 23,
      "coveragePercent": 78.3
    }
  },
  "message": "Score retrieved"
}
필드설명
tierEXCELLENT(750–850) · GOOD(650–749) · FAIR(550–649) · BELOW_AVERAGE(450–549) · POOR(0–449)
reasonCodes사유코드(카탈로그 31종 중), 최대 4건, 영향 순 랭킹
decisionHash결정 프로비넌스 해시(있을 때만). 해시 대상 canonical 페이로드는 불변 — 메타데이터 필드는 해시 대상 밖
environment"LIVE" | "SANDBOX" — 인증된 키의 환경
confidence"HIGH" | "MEDIUM" | "LOW" — 피처 스냅샷 결측 기반. 스냅샷이 없으면 LOW이고 dataSufficiency 생략
contributions동의 데이터 카테고리에 contributions가 포함된 경우에만 존재하는 피처 기여 상세

거부: 동의 없음/철회/만료/주체 없음은 에러 응답(에러)로 거부됩니다. 어떤 사유든 FI가 주체 존재를 추론할 수 없도록 동일하게 처리됩니다.

POST/fi/decision/evaluate — 정책 판정·대출 조건 산출

동의 게이트를 통과한 점수를 기관이 미리 등록해 둔 정책 규칙과 대출 조건표에 대입해 APPROVE / REVIEW / DECLINE 판정과 조건을 돌려줍니다. 점수 조회 경로를 그대로 재사용하므로 동의·접근로그 규칙은 /fi/score/query와 완전히 동일합니다 (동의가 없으면 여기서도 같은 방식으로 거부).

요청

{
  "pairwiseSub": "b41c9d2f-8e77-4a10-9c3e-example",
  "purpose": "UNDERWRITING",
  "productCode": "MICRO_LOAN_12M"
}
필드필수제약
pairwiseSub필수파트너 전용 익명 ID
purpose필수조회 목적. 동의는 (클라이언트, 목적) 단위로 확인됩니다
productCode필수대문자·숫자·밑줄만([A-Z0-9_]), 1~40자. 위반 시 SW1001

응답 200 — 승인

{
  "success": true,
  "data": {
    "pairwiseSub": "b41c9d2f-8e77-4a10-9c3e-example",
    "productCode": "MICRO_LOAN_12M",
    "segmentKey": "KR:MICRO_LOAN_12M",
    "decision": "APPROVE",
    "score": 712,
    "tier": "GOOD",
    "confidence": "HIGH",
    "terms": {
      "depositRatePct": 10.00,
      "creditLimit": 3000000,
      "currency": "KRW",
      "maxTenorMonths": 12,
      "interestAprPct": 14.50
    },
    "firedRules": [],
    "reasonCodes": [
      {
        "code": "RC_LOW_UTILITY_PAYMENT_RELIABILITY",
        "labelEn": "Low utility bill payment reliability",
        "labelLocalized": "전기·수도 등 공과금을 제때 낸 기록이 부족함",
        "category": "PAYMENT",
        "impactPoints": 38,
        "rank": 1
      }
    ],
    "modelVersion": "scorecard-v2.1",
    "scoredAt": "2026-08-20 14:32:11",
    "environment": "LIVE"
  },
  "message": "Decision evaluated"
}

응답 200 — 즉시 거절 (terms는 아예 생략됩니다)

{
  "success": true,
  "data": {
    "pairwiseSub": "b41c9d2f-8e77-4a10-9c3e-example",
    "productCode": "MICRO_LOAN_12M",
    "segmentKey": "GLOBAL",
    "decision": "DECLINE",
    "score": 412,
    "tier": "POOR",
    "confidence": "MEDIUM",
    "firedRules": [
      {
        "ruleCode": "KO_SCORE_FLOOR",
        "ruleType": "KNOCKOUT",
        "messageEn": "Score below product floor"
      }
    ],
    "reasonCodes": [
      {
        "code": "RC_CASHFLOW_INSTABILITY",
        "labelEn": "Cash-flow instability",
        "labelLocalized": "수입·지출 흐름이 불안정함",
        "category": "FINANCIAL",
        "impactPoints": 52,
        "rank": 1
      }
    ],
    "modelVersion": "scorecard-v2.1",
    "scoredAt": "2026-08-20 14:32:11",
    "environment": "LIVE"
  },
  "message": "Decision evaluated"
}
필드설명
decisionAPPROVE · REVIEW(수동 심사) · DECLINE
segmentKey실제로 적용된 세그먼트(예: KR:MICRO_LOAN_12M, KR, GLOBAL)
terms조건표에서 채택된 조건. DECLINE이거나 조건표에 매칭 행이 없으면 생략
terms.depositRatePct보증금(선납금) 비율 %
terms.creditLimit · terms.currency한도 금액과 3자리 통화코드
terms.maxTenorMonths최대 대출 기간(개월)
terms.interestAprPct연이율(APR) %
firedRules발화한 규칙 목록(ruleCode, ruleType=KNOCKOUT|REVIEW, messageEn). 없으면 빈 배열
score·tier·confidence·reasonCodes·modelVersion·scoredAt/fi/score/query와 동일한 값(같은 점수 응답에서 그대로 옵니다)
environment"LIVE" | "SANDBOX"

판정 순서

판정은 결정적(deterministic)입니다 — 같은 점수와 같은 설정이면 항상 같은 결과입니다.

  1. 동의 확인 + 점수 조회: /fi/score/query와 동일한 경로. 동의가 없으면 같은 방식으로 거부됩니다.
  2. 세그먼트 체인 결정: {국가}:{productCode} → {국가} → GLOBAL 순으로, 구체적인 것이 먼저입니다. 국가 컨텍스트가 없으면 GLOBAL 하나만 사용됩니다.
  3. 즉시 거절 규칙(KNOCKOUT): 등록 순서대로 평가하여 첫 매치에서 즉시 DECLINE. 조건표는 보지 않고, terms 없이 해당 규칙 1건만 firedRules에 담아 반환합니다.
  4. 수동 심사 규칙(REVIEW): 매칭된 규칙을 모두 firedRules에 누적합니다(여기서 반환하지는 않음).
  5. 대출 조건표(terms matrix): 세그먼트 체인 순서로, 각 세그먼트마다 기관 전용 행을 먼저 찾고 없으면 공용 행을 찾습니다. 행의 조건은 minScore ≤ score ≤ maxScore 그리고 confidence가 그 행의 minConfidence 이상(LOW < MEDIUM < HIGH)입니다. 첫 매치를 채택합니다.
  6. 조건표가 구성되어 있지 않거나 매칭 행이 없으면 REVIEW — 조건 없이 수동 심사로 안전하게 처리합니다 (임의로 승인하지 않습니다).
  7. 채택된 행의 판정을 사용하되, REVIEW 규칙이 하나라도 발화한 상태에서 행이 APPROVE면 REVIEW로 낮춥니다. 행이 DECLINE이면 terms는 생략됩니다.
규칙과 조건표는 운영팀이 기관별로 등록합니다(관리자 API /admin/hacs/fi-policy-rules, /admin/hacs/fi-terms). 기관 전용 설정이 없으면 공용 설정이 적용됩니다.

규칙 조건에 쓸 수 있는 항목

항목(field)비교 연산자값
scoreLT · LTE · GT · GTE · EQ · NEQ숫자
coveragePercentLT · LTE · GT · GTE · EQ · NEQ숫자(응답의 dataSufficiency.coveragePercent)
tierEQ · NEQ는 이름 비교, 나머지는 등급 순서 비교(POOR < BELOW_AVERAGE < FAIR < GOOD < EXCELLENT)등급 문자열
confidenceEQ · NEQ는 이름 비교, 나머지는 LOW < MEDIUM < HIGH 순서 비교LOW · MEDIUM · HIGH
reasonCodeCONTAINS(해당 사유코드 포함) · EQ · NEQ사유코드 문자열

한 규칙 안의 조건은 모두 만족해야 발화합니다(AND). 형식이 잘못된 규칙은 매치되지 않고 조용히 건너뜁니다.

샌드박스 동작

샌드박스 키로 호출하면 점수는 합성값(샌드박스의 고정 주체 표와 동일)이고, 규칙과 조건표는 실제로 등록된 기관 설정이 그대로 적용됩니다 — 정책을 라이브 전에 그대로 시험해 볼 수 있습니다.

샌드박스 합성 점수에는 국가 컨텍스트가 없으므로 세그먼트 체인은 GLOBAL 하나뿐입니다. 국가별 규칙·조건표를 시험하려면 GLOBAL 세그먼트에 동일한 설정을 두고 확인하세요. SANDBOX-NO-CONSENT·SANDBOX-EXPIRED는 점수 조회와 똑같이 거부됩니다.
curl -s https://trendy.storydot.kr/superapp/api/fi/decision/evaluate \
  -H "X-Client-Id: YOUR_CLIENT_ID" \
  -H "X-API-Key: YOUR_SANDBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "pairwiseSub": "SANDBOX-TIER-C", "purpose": "UNDERWRITING", "productCode": "MICRO_LOAN_12M" }'

사용량 집계에서는 DECISION 버킷으로 계량되며 호출당 2유닛입니다(GET /fi/usage 참고).

POST/fi/score/batch — 배치 점수 조회

한 번의 호출로 1~100건을 조회합니다(100건 초과 시 검증 에러 SW1001). 건별로 단건과 동일한 동의 검사가 적용되며, 거부 건은 status="DENIED" 한 가지 라벨로만 표시됩니다.

요청

{
  "subjects": ["b41c9d2f-8e77-4a10-9c3e-example", "SANDBOX-NO-CONSENT"],
  "purpose": "ACCOUNT_REVIEW"
}

응답 200

{
  "success": true,
  "data": {
    "batchId": "3f8a1b62-90cd-4e5f-a217-7c9d1e2f3a4b",
    "summary": { "requested": 2, "returned": 1, "denied": 1 },
    "results": [
      {
        "pairwiseSub": "b41c9d2f-8e77-4a10-9c3e-example",
        "status": "OK",
        "score": 712,
        "tier": "GOOD",
        "reasonCodes": [
          {
            "code": "RC_LOW_UTILITY_PAYMENT_RELIABILITY",
            "labelEn": "Low utility bill payment reliability",
            "labelLocalized": "Low utility bill payment reliability",
            "category": "PAYMENT",
            "impactPoints": 40,
            "rank": 1
          }
        ],
        "modelVersion": "scorecard-v2.1",
        "scoredAt": "2026-08-20 14:32:11",
        "decisionHash": "9f2ab1c4e5d6...64hex"
      },
      {
        "pairwiseSub": "SANDBOX-NO-CONSENT",
        "status": "DENIED"
      }
    ]
  },
  "message": "Batch completed"
}
DENIED는 동의 없음·철회·만료·주체 없음을 구분하지 않습니다(타이밍도 균일화). 사용자에게 동의를 요청하려면 POST /fi/consent/requests를 사용하세요.

POST/fi/verify — 신용등급 증명서(어테스테이션) 검증

사용자가 제시한(QR 스캔 등) 증명서가 발급기관 원본과 일치하는지 확인합니다. 동의 불필요 — 제시받은 값 이상은 아무것도 공개되지 않습니다. 위·변조 증명서도 에러가 아닌 200 응답에 valid=false로 표시됩니다.

요청 — vcId/reputationHash 중 최소 1개 필수, 나머지는 선택 교차검증

{
  "vcId": "vc-8c3a51d0-example",
  "reputationHash": "0x4e5f6a7b...",
  "tierCommitment": "0x1a2b3c...",
  "tier": "GOOD"
}

응답 200

{
  "success": true,
  "data": {
    "valid": true,
    "reason": "VALID",
    "tier": "GOOD",
    "status": "ANCHORED",
    "issuedAt": "2026-07-01 10:00:00",
    "expiresAt": "2026-09-29 10:00:00",
    "anchoredTxHash": "0x7d1e...",
    "modelVersion": "scorecard-v2.1"
  },
  "message": "Attestation verification completed"
}
reason의미
VALID발급 원본과 일치하고 유효기간(발급 후 90일) 이내
NOT_FOUND제시된 vcId/reputationHash와 일치하는 발급 기록 없음
REVOKED발급 기록이 폐기됨
EXPIRED유효기간(90일)이 지남
COMMITMENT_MISMATCH제시된 tierCommitment가 발급 기록과 다름(변조 의심)
TIER_MISMATCH제시된 tier가 발급 기록과 다름

GET/fi/score/history — 점수 이력 조회

GET /fi/score/history?pairwiseSub=&fromDate=&toDate= (조회 구간 최대 365일)

동의의 데이터 카테고리에 history가 포함되어야 합니다. 없으면 동의 없음 계열로 거부됩니다.

응답 200

{
  "success": true,
  "data": {
    "pairwiseSub": "b41c9d2f-8e77-4a10-9c3e-example",
    "entries": [
      {
        "score": 712,
        "tier": "GOOD",
        "modelVersion": "scorecard-v2.1",
        "scoredAt": "2026-08-20 14:32:11",
        "decisionHash": "9f2ab1c4e5d6...64hex",
        "anchorTxHash": "0x7d1e..."
      },
      {
        "score": 688,
        "tier": "GOOD",
        "modelVersion": "scorecard-v2.0",
        "scoredAt": "2026-07-19 09:12:45",
        "decisionHash": "5b6c7d8e9f0a...64hex",
        "anchorTxHash": null
      }
    ]
  },
  "message": "History retrieved"
}

GET/fi/whoami — 내 클라이언트 확인

인증만 필요하고 동의는 필요 없습니다. 키가 어떤 클라이언트·환경에 결속됐는지 반환합니다.

{
  "success": true,
  "data": {
    "clientId": "bank-busan-001",
    "name": "Example Bank",
    "environment": "SANDBOX",
    "rateLimitTier": "STANDARD",
    "status": "ACTIVE"
  },
  "message": "OK"
}

GET/fi/usage — 사용량 자기조회

GET /fi/usage?fromDate=&toDate= (조회 구간 최대 92일)

일별·엔드포인트별 호출/유닛 집계를 반환합니다. 유닛 가중치: score/query=1, batch 건당=1, verify=1, history=2, decision=2.

{
  "success": true,
  "data": {
    "clientId": "bank-busan-001",
    "days": [
      { "date": "2026-08-20", "endpoint": "QUERY", "callCount": 41, "unitCount": 41 },
      { "date": "2026-08-20", "endpoint": "BATCH", "callCount": 2, "unitCount": 130 },
      { "date": "2026-08-20", "endpoint": "HISTORY", "callCount": 3, "unitCount": 6 },
      { "date": "2026-08-20", "endpoint": "DECISION", "callCount": 9, "unitCount": 18 }
    ]
  },
  "message": "Usage retrieved"
}

endpoint 값: QUERY | BATCH | VERIFY | HISTORY | DECISION. 집계는 매일 03:30에 전일 접근로그 기준으로 계산됩니다.

POST/fi/consent/requests — 동의 요청

사용자 앱으로 점수 공유 동의를 요청하는 인앱 알림을 보냅니다.

{
  "pairwiseSub": "b41c9d2f-8e77-4a10-9c3e-example",
  "purpose": "UNDERWRITING"
}

응답 200: data=null, message="Consent request sent"

GET/fi/consent/{consentReceiptId} — 동의 상태 조회

{
  "success": true,
  "data": {
    "consentReceiptId": "cr-1f2e3d4c-example",
    "pairwiseSub": "b41c9d2f-8e77-4a10-9c3e-example",
    "clientId": "bank-busan-001",
    "purpose": "UNDERWRITING",
    "scope": "score",
    "dataCategories": "score,tier,reason-codes",
    "status": "ACTIVE",
    "grantedAt": "2026-08-01 11:20:00",
    "expiresAt": "2026-11-01 11:20:00"
  },
  "message": "Consent status retrieved"
}

status: ACTIVE | REVOKED | EXPIRED. dataCategories는 CSV 문자열이며 기본값은 score,tier,reason-codes, 선택 확장으로 contributions, history, monitoring이 있습니다(동의 모델 참고).

GET/fi/health — 상태 확인 (공개)

인증 불필요. 개인정보 없음.

{
  "success": true,
  "data": { "status": "UP", "pullTypes": "SOFT" },
  "message": "OK"
}

관리자(운영) 전용 엔드포인트

아래 엔드포인트는 파트너 키가 아닌 관리자 JWT(ADMIN)로만 접근 가능한 내부 운영 API입니다. 파트너 통합에는 필요하지 않으며, 계약의 완전성을 위해 기재합니다.

메서드·경로설명
GET·POST /api/v1/admin/hacs/fi-policy-rules
PUT·DELETE /api/v1/admin/hacs/fi-policy-rules/{id}
판정 규칙(KNOCKOUT·REVIEW) 조회·등록·수정·삭제 — /fi/decision/evaluate가 사용하는 설정
GET·POST /api/v1/admin/hacs/fi-terms
PUT·DELETE /api/v1/admin/hacs/fi-terms/{id}
대출 조건표 행 조회·등록·수정·삭제
GET /api/v1/admin/hacs/fi-usage?clientId=&fromDate=&toDate=클라이언트별 사용량 조회
POST /api/v1/admin/hacs/fi-usage/aggregate?date=특정 일자 수동 재집계
GET /api/v1/admin/hacs/fi-webhooks웹훅 엔드포인트 목록
POST /api/v1/admin/hacs/fi-webhooks생성(url, events, clientId) — 서명 시크릿은 서버 생성 후 1회 평문 반환
PUT /api/v1/admin/hacs/fi-webhooks/{id}상태 변경·재활성(연속 실패 카운터 리셋)
DELETE /api/v1/admin/hacs/fi-webhooks/{id}삭제
POST /api/v1/admin/hacs/fi-webhooks/{id}/test테스트 이벤트 발송
GET /api/v1/admin/hacs/fi-webhooks/{id}/deliveries최근 전달 기록 50건
GET /api/v1/admin/hacs/fi-access-logs/export?clientId=&fromDate=&toDate=접근로그 CSV(헤더: accessedAt,clientId,subjectPairwiseSub,purpose,outcome,returnedTier,modelVersion,environment,batchId,entryHash)
GET /api/v1/admin/hacs/fi-consents/export?clientId=동의 CSV(헤더: grantedAt,revokedAt,clientId,pairwiseSub,purpose,dataCategories,status,receiptId)