# 요집 × 리치고 연동 가이드

> 이 문서 하나로 리치고 부동산 데이터를 요집 화면에 붙일 수 있습니다.
> AI 코딩 도구에 이 파일을 그대로 물려도 됩니다. 추측이 필요한 부분은 남기지 않았습니다.

- 작성 2026-09-02 · 대상 데모: [요집 × 리치고 데모 화면 5종](https://claude.ai/code/artifact/bafcf1cd-5cff-4981-b9bc-313c9efa087e)
- 문의: 리치고(데이터노우즈) 개발팀

---

## 0. 30초 요약

| 구분 | 누가 준비 | 비고 |
|---|---|---|
| 시세·실거래·호가·AI예측·학군·호재·시황 | **리치고가 제공** | 파트너 API 키 1개면 끝 |
| 등기부등본 발급 | **요집이 직접 계약** | 재판매 불가. 3절 참고 |
| 문자·알림톡 발송 | **요집이 직접 계약** | 발신프로필·템플릿 승인 필요. 3절 참고 |
| AI 서술문(LLM) 생성 | **요집 자체 키** | 리치고 API는 LLM을 호출하지 않음. 4절 참고 |
| 결제(PG) | **요집이 직접 계약** | 최종 사용자에게 과금할 경우 |

**중요**: API 시크릿은 절대 브라우저로 내려보내지 마세요. 반드시 요집 서버(BFF)에서만 호출합니다.

---

## 1. 인증

### 1.1 준비물 (리치고가 1회 발급)

```
RICHGO_PARTNER_KEY=rgk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
RICHGO_PARTNER_SECRET=<발급 시 한 번만 표시됩니다. 분실하면 재발급뿐입니다>
```

`apiSecret`은 리치고 DB에 PBKDF2 해시로만 저장되므로 리치고도 원문을 복구할 수 없습니다.

### 1.2 토큰 발급

```http
POST https://api-m.richgo.ai/v2/Authen/partner/token
Content-Type: application/json

{ "apiKey": "rgk_...", "apiSecret": "..." }
```

응답 (봉투 없이 최상위):

```json
{
  "accessToken": "eyJ...",
  "refreshToken": "eyJ...",
  "expiresIn": 86400,
  "issuedAt": 1788323973136,
  "accessTokenExpiresAt": 1788410373136,
  "refreshTokenExpiresAt": 1790915973136
}
```

액세스 토큰에는 `PartnerCompanyCode`, `PartnerApiKeyId` 클레임이 들어 있어
어느 회원사의 호출인지 서버가 식별합니다.

### 1.3 갱신

```http
POST https://api-m.richgo.ai/v2/Authen/partner/token/refresh
Authorization: Bearer <refreshToken>
```

**액세스 토큰은 한 번에 하나만 유효합니다.** 새로 발급받으면 이전 토큰은 죽습니다.
서버 인스턴스가 여러 대면 토큰을 공유 저장소(Redis 등)에 두고 한 곳에서만 갱신하세요.
각 인스턴스가 따로 갱신하면 서로의 토큰을 무효화시켜 401이 계속 납니다.

### 1.4 호출

```http
GET https://api-m.richgo.ai/v2/Partner/danji/a9rJSEi
Authorization: Bearer <accessToken>
```

### 1.5 응답 형태 — 파트너 API에는 봉투가 없습니다

**`/v2/Partner/*` 와 `/v2/Authen/partner/*` 는 데이터를 최상위로 바로 반환합니다.**
`result` 로 감싸지 않습니다. (2026-09-02 운영 실호출로 확인)

```jsonc
// GET /v2/Partner/danji/a9rJSEi  →  이렇게 옵니다
{ "danjiId": "a9rJSEi", "danjiName": "한화포레나포항", "constructYear": 2024, ... }

// 이렇게 오지 않습니다
{ "statusCode": 200, "result": { ... } }   // ❌
```

> ⚠️ 리치고 **내부** API(`/api/data/*`)는 `{ version, statusCode, message, responseException, result }`
> 봉투를 씁니다. 사내 코드나 다른 문서를 참고하다 이 습관을 그대로 가져오면
> 전부 `undefined` 가 됩니다. **파트너 API에서는 `.result` 를 벗기지 마세요.**

방어적으로 짜려면 "봉투가 있으면 벗기고 없으면 그대로" 정도로 처리하면 양쪽 모두 안전합니다
(부록 A의 예제가 그렇게 되어 있습니다).

에러도 봉투 없이 최상위로 옵니다:

```json
{ "error": "NOT_FOUND", "message": "해당 단지 정보를 찾을 수 없습니다." }
```

### 1.6 호출 제한

회원사 단위로 **초당 10회 / 일 10,000회**입니다. 키를 재발급해도 카운터는 초기화되지 않습니다.

응답 헤더로 잔량이 내려옵니다:

```
X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 9873
X-RateLimit-Reset: 43200      # 초 단위, 자정 리셋까지
Retry-After: 1                # 429일 때만
```

### 1.7 에러

| HTTP | error | 대응 |
|---|---|---|
| 401 | `UNAUTHORIZED` / `INVALID_TOKEN_TYPE` / `INVALID_CLAIMS` | 토큰 1회 갱신 후 재시도. 또 실패하면 중단하고 알림 |
| 429 | `RATE_LIMIT_EXCEEDED` | `Retry-After` 만큼 대기 후 재시도 |
| 400 | `INVALID_REQUEST` | 요청 파라미터 오류. 재시도 금지 |
| 404 | `NOT_FOUND` | 해당 단지·데이터 없음. **정상 상태일 수 있음**(빈 화면 처리) |

> 401에서 무한 재시도하지 마세요. 갱신은 1회만 시도하고 실패하면 멈춰야 합니다.
> 토큰 발급 자체에도 IP당 분당 10회 제한이 있습니다.

---

## 2. 단지 식별자

리치고의 1급 식별자는 **`danjiId`** 입니다. 7자 고정 영숫자(예: `a9rJSEi`)이며 네이버 단지 ID와 다릅니다.

먼저 검색해서 `danjiId`를 얻고, 그걸로 나머지를 호출합니다.

```http
GET /v2/Partner/danji?sd=경북&sgg=포항시 북구&name=한화포레나포항
```

```json
{
  "totalCount": 2, "page": 1, "size": 20,
  "items": [
    { "danjiId": "a9rJSEi", "danjiName": "한화포레나포항", "sd": "경북", "sgg": "포항시 북구",
      "emd": "흥해읍", "bjdCode": "47113253", "address": "...", "totalHouseholds": 1240, "constructYear": 2024 }
  ]
}
```

- 지역(`sd`/`sgg`/`emd`/`bjdCode`) 또는 `name` 중 **최소 하나**는 필수입니다.
  지역 조건 없이 `name`만으로 검색하려면 **2글자 이상**이어야 합니다.
- `size` 기본 20, 최대 100.
- **`danjiId`는 요집 DB에 캐시해 두세요.** 매번 검색하면 호출 쿼터가 빨리 소진됩니다.

### 시도(sd) 표기

`서울`, `경기`, `경북` 처럼 **축약형**을 씁니다(`서울특별시` ❌, `경상북도` ❌).
예외: 전라남도 광주시는 `전남광주`, 광주광역시는 `광주`입니다.

---

## 3. 요집이 직접 계약·발급해야 하는 것

리치고가 대신 해줄 수 없는 항목입니다. 리치고 계정을 공유하는 형태로는 재판매가 불가능합니다.

### 3.1 등기부등본 — 필수 계약 3건

보증금 안전도 화면(중개사용 02)에 등기부가 필요합니다. 리치고는 **등기부를 발급해 주지 않습니다.**

| 필요한 것 | 무엇 | 왜 필요 |
|---|---|---|
| **등기 발급 API 벤더** | 하이픈(Hyphen), 코드에프(CODEF) 등 국내 등기 발급 대행 API | 등기부 원문 + PDF를 받아오는 채널 |
| **인터넷등기소 회원 계정 + 전자지갑 잔액** | `iros.go.kr` 계정. 벤더 API에 이 계정 자격을 넘겨 열람합니다 | **실제 열람 수수료가 이 계정에서 빠집니다** |
| **부동산 고유번호 조회 수단** | 벤더가 제공하거나 인터넷등기소에서 직접 조회 | 등기부를 뽑으려면 고유번호가 먼저 필요 |

참고로 리치고 서비스의 소비자 가격은 등기부 최신 발급 1,500원 / 과거 등기 500원입니다.
요집의 원가는 벤더 계약 단가 + 대법원 열람 수수료로 정해지며 리치고와 무관합니다.

**등기 변동 감시**(계약~잔금 사이 근저당 신규 설정 알림)를 하려면 등기신청사건 처리현황을 조회하는
별도 벤더(예: 틸코)가 추가로 필요하고, 주기적으로 도는 배치를 요집이 운영해야 합니다.

### 3.2 문자·알림톡 발송

리치고 서버에는 **SMS 발송 기능이 없습니다.** 카카오 알림톡만 씁니다.

| 필요한 것 | 비고 |
|---|---|
| **알림톡 발송 대행사 계약** | NHN Cloud(TOAST), 솔라피, 알리고 등 |
| **카카오 비즈니스 채널 + 발신프로필 등록** | 발신프로필 키(`senderKey`)는 채널 소유자 것만 씁니다. 리치고 것 공유 불가 |
| **템플릿 사전 승인** | 알림톡은 자유 문구를 못 보냅니다. 템플릿을 미리 등록·승인받고 변수만 채웁니다 |
| **SMS 발송 시 발신번호 사전등록** | 알림톡 대신 SMS를 쓸 경우. 통신사 사전등록 절차 필요 |

승인까지 보통 **영업일 기준 3~5일**이 걸리므로 일정에 미리 반영하세요.

### 3.3 결제(PG)

최종 사용자에게 과금한다면 요집 명의의 PG 계약(토스페이먼츠 등)이 필요합니다.

### 3.4 푸시

앱 푸시를 쓴다면 요집의 Firebase 프로젝트 + (선택) AWS SNS 플랫폼 애플리케이션이 필요합니다.

---

## 4. AI 키 — 요집 것을 씁니다

**리치고 파트너 API는 런타임에 LLM을 호출하지 않습니다.** AI 미래가격은 배치로 미리 계산된
수치를 읽어 오는 것이라, 요집이 아무리 많이 호출해도 리치고 AI 비용이 발생하지 않고
응답도 빠릅니다(수십 ms).

데모 화면의 서술형 문장(예: "입주물량이 2024년 9,084세대에서 2026년 3,100세대로 빠르게 줄고…")처럼
**문장을 생성하고 싶다면 요집이 발급한 LLM 키로 요집 서버에서 생성**하세요.
리치고 API가 준 수치를 프롬프트에 넣으면 됩니다.

```
# 요집 .env (예시)
YOZIP_LLM_API_KEY=sk-...        ← 요집이 발급·부담
RICHGO_PARTNER_KEY=rgk_...      ← 리치고가 발급
RICHGO_PARTNER_SECRET=...
```

서술문 생성 프롬프트 예시:

```
아래는 아파트 단지의 실측 지표다. 부동산 중개사가 고객에게 보여줄 리포트에 들어갈
2~3문장 요약을 써라. 수치를 지어내지 말고 주어진 값만 사용하라. 단정적 투자 권유는 하지 마라.

단지: {danjiName} {pyeongType}평
현재 시세: {basePrice}만원
3개월 예측: {price3m}만원 ({rate3m}%)
2년 예측: {price2y}만원 ({rate2y}%)
최근 실거래: {lastTradePrice}만원 ({lastTradeDate})
현재 최저 호가: {minOfferPrice}만원 (매물 {volume}건)
```

> 리포트에 반드시 함께 표기: "AI 미래가격은 과거 시세·거래량·공급·금리 등을 학습한 예측치이며
> 투자 결과를 보장하지 않습니다. 데이터 제공: 리치고(RichGo)"

---

## 5. 화면별 필요 API

데모 5개 화면을 기준으로 어떤 엔드포인트를 조합하는지 정리했습니다.

| 화면 | 필요 호출 | 요집 자체 준비 |
|---|---|---|
| **중개사 01** AI 미래가격 리포트 | `danji/{id}`, `danji/{id}/ai-price`, `danji/{id}/price-series`, `danji/{id}/trades`, `danji/{id}/region-stats` | (선택) LLM 서술문 |
| **중개사 02** 보증금 안전도 | `POST deposit-safety` | **등기부 발급** → 채무액 추출 |
| **일반 01** 호재 | `danji/{id}/goodnews` | — |
| **일반 02** 학군 | `danji/{id}/schools` | — |
| **일반 03** 시황 히트맵 | `region/heatmap` | — |

호출 수: 화면 1이 5회로 가장 많고 나머지는 1회씩입니다. 단지 상세 1회 조회 = 최대 5콜이므로
일 10,000콜이면 **하루 약 2,000 단지 조회**가 가능합니다. 캐시를 붙이면 훨씬 여유롭습니다.

**캐싱 권장치** (데이터 갱신 주기 기준):

| 데이터 | 권장 TTL |
|---|---|
| 단지 기본정보, 학군 | 7일 |
| 호재 | 1일 |
| AI 미래가격, 실거래, 시계열, 히트맵 | 1일 (배치가 하루 1회 갱신) |
| 매물 통계(호가) | 1~6시간 |

---

## 6. 엔드포인트 레퍼런스

모든 금액은 **만원** 단위입니다. 단 히트맵의 `capPrice`(시가총액)만 **원** 단위입니다.
비율은 퍼센트(%)이고, `null`은 "값 없음"이며 오류가 아닙니다.

### 6.1 `GET /v2/Partner/danji` — 단지 검색

| 파라미터 | 타입 | 설명 |
|---|---|---|
| `sd` `sgg` `emd` | string | 지역명 |
| `bjdCode` | string | 법정동 코드 |
| `name` | string | 단지명 부분 일치. 지역 조건 없이 쓰려면 2글자 이상 |
| `page` | int | 기본 1 |
| `size` | int | 기본 20, 최대 100 |

### 6.2 `GET /v2/Partner/danji/{danjiId}` — 단지 기본 정보

```json
{ "danjiId":"a9rJSEi","danjiName":"한화포레나포항","constructYear":2024,"totalHouseholds":1240,
  "floorTop":30,"floorBot":1,"totalParking":1500,"floorAreaRatio":220,"buildingCoverageRatio":18,
  "constructName":"한화건설" }
```

### 6.3 `GET /v2/Partner/danji/{danjiId}/ai-price` — AI 미래가격 🆕

| 파라미터 | 기본값 |
|---|---|
| `pyeongType` | 단지 대표 평형 |

```json
{
  "danjiId": "a9rJSEi", "danjiName": "한화포레나포항", "pyeongType": 33,
  "basePrice": 40946,
  "predictedAt": "2026-08-23T19:04:24",
  "points": [
    { "horizon": "3M", "price": 41549, "rate": 1.47 },
    { "horizon": "2Y", "price": 45420, "rate": 10.93 },
    { "horizon": "4Y", "price": 51733, "rate": 26.34 }
  ]
}
```

- `basePrice`는 예측의 기준이 된 리치고 매매 시세입니다. `rate`는 이 값 대비 증감률입니다.
- **아직 예측이 없는 평형이 있습니다.** 그럴 땐 `points`의 `price`/`rate`가 전부 `null`로 옵니다.
  오류가 아니라 "이 평형은 예측 대상이 아님"이라는 뜻이니 화면에서 해당 블록을 감추면 됩니다.
- **모델별 적중률은 제공하지 않습니다.** 리치고에 예측 모델 라인업과 과거 성적이 있긴 하지만
  그건 KB 연계용 산출물(시군구 단위)이고, 여기서 주는 가격은 단지·평형 단위의 다른 파이프라인입니다.
  같은 단지에서 두 값이 맞지 않고 모델이 커버하지 않는 단지도 가격은 나오기 때문에,
  가격 옆에 적중률을 붙이면 그 모델이 이 숫자를 냈다는 잘못된 뜻이 됩니다.
  화면에 신뢰도 수치를 표기하지 마세요.

### 6.4 `GET /v2/Partner/danji/{danjiId}/price-series` — 실거래·호가 시계열 + 호가 분포 🆕

| 파라미터 | 기본값 | 상한 |
|---|---|---|
| `pyeongType` | 대표 평형 | — |
| `months` | 36 | 120 |

```json
{
  "danjiId":"a9rJSEi","danjiName":"한화포레나포항","pyeongType":33,"months":36,
  "series": [
    { "month":"2026.06","tradeAvgPrice":42400,"tradeCount":2,"opengoodsMinPrice":37500,"opengoodsVolume":66 },
    { "month":"2026.07","tradeAvgPrice":38950,"tradeCount":2,"opengoodsMinPrice":37000,"opengoodsVolume":69 },
    { "month":"2026.08","tradeAvgPrice":null,"tradeCount":0,"opengoodsMinPrice":38000,"opengoodsVolume":69 }
  ],
  "opengoodsBands": [
    { "price":38000,"count":2,"isLowest":true },
    { "price":38500,"count":4,"isLowest":false },
    { "price":42000,"count":10,"isLowest":false }
  ]
}
```

- `series`는 **오래된 달 → 최근 달** 순이고, 거래가 없는 달도 `tradeCount:0`으로 채워져 옵니다
  (차트에서 x축 간격이 깨지지 않도록).
- ⚠️ **실거래와 호가는 기준일이 다릅니다.** 실거래는 국토부 신고 기준이라 최대 30일 신고 지연이 있고,
  호가는 매물 수집 시점입니다. 같은 달 값을 1:1로 비교해 "호가가 실거래보다 높다/낮다"를
  단정하지 마세요. 추세 비교용으로만 쓰는 것이 안전합니다.
- `opengoodsBands`는 **현재 시점** 매매 호가 분포입니다(시계열 아님). 히스토그램에 바로 씁니다.

### 6.5 `GET /v2/Partner/danji/{danjiId}/trades` — 실거래 내역

| 파라미터 | 기본값 | 상한 |
|---|---|---|
| `pyeongType` | 전체 | — |
| `months` | 24 | 60 |

```json
{ "danjiId":"a9rJSEi","danjiName":"한화포레나포항","months":24,"totalCount":8,"isTruncated":false,
  "items":[ { "tradeDate":"2026-07-14","pyeongType":33,"price":37000,"floor":29,
              "supplyArea":110.5,"privateArea":84.98,"dong":"108","transactionType":"중개거래" } ] }
```

취소된 거래는 제외되어 옵니다. 한 번에 최대 3,000건이며 잘리면 `isTruncated:true`입니다.

### 6.6 `GET /v2/Partner/danji/{danjiId}/schools` — 학군

| 파라미터 | 기본값 |
|---|---|
| `top` | 학교급별 상위 N개 |

```json
{ "danjiId":"a9rJSEi","danjiName":"한화포레나포항",
  "elementarySchools":[ { "schoolId":"...","schoolName":"달전초등학교","schoolLevel":"초등학교",
    "foundationType":"공립","coedSchool":"남여공학","distanceEuclidean":1049,"address":"...",
    "totalStudent":477,"totalTeacher":30,"studentPerClassroom":21.7,"studentPerTeacher":15.9,
    "baseYear":2025,"admissionRate":1.46,"sdAdmissionRate":22.22,"nationalAdmissionRate":18.4,
    "sdDeciles":2.2,"nationalDeciles":1.8,
    "detailUrl":"https://m.richgo.ai/realty/place/elementary/S150003568" } ],
  "middleSchools":[ ... ], "highSchools":[ ... ] }
```

⚠️ **해석 주의 (자주 틀리는 부분)**

- `detailUrl` 은 리치고 학교 상세(학군·학원가·진학률) 주소입니다. 학교 카드를 누르면 이 주소로
  보내면 됩니다. 학교급 코드가 02/03/04 가 아니면 `null` 이니 링크를 걸기 전에 값 유무를 확인하세요.

- `distanceEuclidean`은 **직선거리(m)** 입니다. 도보 거리가 아닙니다. 도보 시간을 표기하려면
  요집 쪽 경로 API를 쓰거나, "직선 1,049m"처럼 직선임을 밝혀 주세요.
- `sdAdmissionRate` / `nationalAdmissionRate`는 **상위 백분위**입니다. **작을수록 상위**입니다.
  22.22 = 상위 22%. 이 값을 "진학률"로 표시하면 정반대로 읽힙니다.
- `admissionRate`가 진짜 진학률(%)인데 **학교급마다 정의가 다릅니다**:
  초등학교 = 배정 중학교의 특목·자사고 진학률(그 초등학교 자체 실적이 아님),
  중학교 = 특목·자사고 진학률, 고등학교 = 4년제 대학 진학률.
  **학교급이 다른 학교끼리 이 값을 비교하지 마세요.**

### 6.7 `GET /v2/Partner/danji/{danjiId}/goodnews` — 개발 호재

| 파라미터 | 기본값 | 상한 |
|---|---|---|
| `maxDistance` | 1000 | **1000 (m)** |

```json
{ "danjiId":"a9rJSEi","danjiName":"한화포레나포항","maxDistance":1000,
  "summary":{ "goodnewsScore":72,"totalCount":1,"trafficCount":0,"houseCount":0,"jiguCount":1,"etcCount":0,
              "trafficRegionMean":1.2,"houseRegionMean":0.4,"jiguRegionMean":0.3,"etcRegionMean":0.1 },
  "items":[ { "goodnewsId":"...","type":"택지","subType":"산업단지","detailType":null,
              "name":"포항융합기술산업지구","score":72,"distance":819.0,"distanceGroup":1000,
              "detailUrl":"https://m.richgo.ai/realty/place/jigu/47113MX2010001" } ] }
```

- `detailUrl` 은 리치고 호재 상세 주소입니다. 호재를 누르면 이 주소로 보내면 됩니다.
  중분류별로 경로가 달라(정비사업 `redevelop`/`rebuilding`/`remodeling`/`moatown`, 택지 `jigu`,
  도로 `road`, 지하철·철도 `subwayStation`) 서버가 만들어 주니 직접 조립하지 마세요.
  중분류를 모르는 값이면 `null` 이니 링크를 걸기 전에 확인하세요.
- **반경 상한이 1km입니다.** 데모 화면의 "2km" 토글은 1km까지만 실제 조회됩니다.
  UI에서 2km 옵션을 두려면 1km 결과에 안내 문구를 붙이거나 옵션을 600m/1km로 바꾸세요.
- `distanceGroup`은 300 / 600 / 1000 / 3000 중 하나로 버킷팅된 값입니다(거리 링 UI용).
- `*RegionMean`은 그 지역 평균 호재 건수로, `maxDistance`와 무관한 기준값입니다.
  "우리 단지가 평균보다 많다/적다"를 보여줄 때 씁니다.

### 6.8 `GET /v2/Partner/region/heatmap` — 시황 히트맵 🆕

| 파라미터 | 필수 | 설명 |
|---|---|---|
| `sd` | ✅ | 시도 축약형 (`경북`) |
| `sgg` | | 생략하면 시도 전체 |

```json
{ "sd":"경북","sgg":"포항시 북구","totalCount":24,"isTruncated":false,
  "items":[ { "danjiId":"a9rJSEi","danjiName":"한화포레나포항","sd":"경북","sgg":"포항시 북구",
              "emd":"흥해읍","pyeongType":33,"minPrice":38000,"capPrice":87372691,
              "rate1M":0.0,"rate3M":1.3,"rate6M":0.0,"rate12M":-5.0 } ] }
```

- 증감률은 전부 **매매 최저 호가** 기준입니다. 실거래 증감률이 아닙니다. 라벨에 "호가"를 명시하세요.
- `capPrice`는 **원** 단위 시가총액입니다(다른 금액과 단위가 다릅니다). 타일 크기·정렬에 씁니다.
- 시가총액 내림차순, 최대 500건. 넘치면 `isTruncated:true`.
- n개월 전 값이 없는 신축 단지는 해당 `rate`가 `null`입니다. 0%로 칠하지 말고 회색 처리하세요.

**히트맵 색 스케일 팁**: 최댓값에 맞춰 정규화하면 이상치 1~2개가 스케일을 끌고 가서 나머지가
전부 무채색이 됩니다. 절댓값 **상위 20% 지점**을 포화 기준으로 잡으면 대비가 살아납니다
(데모가 그렇게 되어 있습니다).

### 6.9 `POST /v2/Partner/deposit-safety` — 보증금 안전도 🆕

**등기부는 리치고가 발급하지 않습니다.** 요집이 발급한 등기부에서 읽은 값을 요청에 담아 보내면,
리치고가 시세를 채우고 안심전세와 동일한 기준으로 판정해 돌려줍니다.

요청:

```json
{
  "danjiId": "a9rJSEi",
  "pyeongType": 33,
  "deposit": 20000,
  "seniorDebt": 0,
  "juniorDebt": 0,
  "hasAdverseRegistryEntry": false,
  "isMetropolitan": null,
  "contractDate": "2026-08-11T00:00:00+09:00"
}
```

| 필드 | 출처 | 설명 |
|---|---|---|
| `deposit` | 계약서 | 보증금(만원) |
| `seniorDebt` | **등기부 을구** | 계약일 **이전** 설정되고 말소되지 않은 채권최고액 합계(만원) |
| `juniorDebt` | **등기부 을구** | 계약일 **이후** 설정된 권리 합계(만원). 판정에는 미사용, 표시용 |
| `hasAdverseRegistryEntry` | **등기부 갑·을구** | 경매개시결정·압류·가압류·가처분·가등기가 하나라도 있으면 `true` |
| `isMetropolitan` | 선택 | 생략하면 단지 주소(서울·경기·인천)로 자동 판단 |

응답:

```json
{
  "danjiId":"a9rJSEi","danjiName":"한화포레나포항","pyeongType":33,
  "contractDate":"2026-08-11T00:00:00+09:00",
  "memePrice":40946,"jeonsePrice":23367,"priceSource":"RICHGO_SISE",
  "deposit":20000,"seniorDebt":0,"juniorDebt":0,
  "debtRatio":49,"debtGrade":"GOOD",
  "expectedAuctionPrice":28663,"expectedLoss":0,"expectedRecovery":20000,"spare":8663,
  "hugEligible":true,
  "hugConditions":[
    { "condition":"보증금이 비수도권 5억 이하인가","passed":true,"detail":"20000 ≤ 50000 (만원)" },
    { "condition":"선순위 채무가 주택가격의 90% 중 60% 이하인가","passed":true,"detail":"0 ≤ 22110 (만원)" },
    { "condition":"선순위 채무와 보증금의 합이 매매 시세의 90% 이하인가","passed":true,"detail":"20000 ≤ 36851 (만원)" },
    { "condition":"등기부에 경매·압류·가압류·가처분·가등기가 없는가","passed":true,"detail":"해당 권리 없음" }
  ],
  "hugFeeMin":46,"hugFeeMax":62,
  "reverseJeonseGrade":"GOOD","jeonseGap":3367
}
```

**판정식** (요집이 직접 재현하거나 검증할 때 쓰세요):

```
매매시세          memePrice        ← 리치고 시세, 없으면 최저 호가(priceSource로 구분)
경매 예상 낙찰가   ceil(memePrice × 0.7)
시세 대비 채무율   ceil((seniorDebt + deposit) / memePrice × 100)
등급              < 50% GOOD · < 70% WARNING · ≥ 70% DANGER
여유분 spare      낙찰가 − seniorDebt − deposit
손실 예상액       max(−spare, 0)
회수 예상액       max(deposit − 손실, 0)

HUG 4개 조건 (모두 통과해야 hugEligible = true)
 1) deposit ≤ (수도권 70000 : 비수도권 50000)
 2) seniorDebt ≤ floor(memePrice × 0.9 × 0.6)
 3) seniorDebt + deposit ≤ floor(memePrice × 0.9)
 4) hasAdverseRegistryEntry == false
HUG 예상 가입비   ceil(deposit × 0.00115 × 2) ~ ceil(deposit × 0.00154 × 2)   (2년치)

역전세            jeonseGap = jeonsePrice − deposit;  < 0 이면 WARNING, 아니면 GOOD
```

- `jeonsePrice`가 `null`이면 `reverseJeonseGrade`도 `null`입니다. **0으로 취급하지 마세요** —
  전세 시세가 없는 것을 "보증금보다 낮다"로 읽으면 없는 위험을 만들어 냅니다. 해당 카드를 감추세요.
- 시세가 아예 없는 단지는 `404 NOT_FOUND`입니다.
- 화면에 **"본 판정은 참고자료이며 법적 효력을 갖지 않습니다"** 를 반드시 표기하세요.

### 6.10 그 밖에 쓸 수 있는 것

| 경로 | 용도 |
|---|---|
| `danji/{id}/region-stats` | 시군구 12개월 거래량·매물량, 36개월 입주물량 |
| `danji/{id}/opengoods-stats` | 현재 매매/전세 매물 최고·최저가, 건수, 1개월 증감률 |
| `danji/{id}/nearby-infra` | 인근 지하철·학교·생활시설 (거리순) |

---

## 7. 구현 시 지켜야 할 것

### 7.1 서버에서만 호출

```
[브라우저] → [요집 BFF/route handler] → [리치고 API]
                     ↑ 여기서만 시크릿·토큰 보관
```

브라우저에 `apiSecret`이나 `accessToken`이 노출되면 요집 쿼터로 제3자가 호출할 수 있습니다.
Next.js라면 route handler 또는 server action, 그 외 프레임워크도 서버 라우트를 하나 두세요.

### 7.2 표기 의무

- 데이터 출처 **"데이터 제공: 리치고(RichGo)"** 를 화면에 표기해 주세요.
- AI 예측에는 투자 보장 부인 문구를 함께 넣어 주세요.
- 등기부 기반 판정에는 법적 효력 부인 문구를 넣어 주세요.

### 7.3 하지 말아야 할 것

- 응답 전체를 그대로 재배포·재판매 (계약 범위 밖)
- 지도 전체를 훑는 식의 대량 수집 — 운영 서버를 함께 쓰고 있습니다
- 401에서 무한 재시도

---

## 8. 연동 순서 제안

1. 리치고에서 **API 키/시크릿 수령** → 요집 서버 환경변수에 저장
2. 토큰 발급/갱신 래퍼 1개 작성 (공유 저장소에 토큰 캐시)
3. `GET /v2/Partner/danji` 로 대상 단지 `danjiId` 확보 → 요집 DB에 저장
4. **일반 고객용 3종 먼저** 붙이기 — 호재·학군·히트맵은 외부 계약이 필요 없어 바로 붙습니다
5. **중개사용 01(AI 미래가격)** — 호출 5개 조합. LLM 서술문은 선택
6. **중개사용 02(보증금 안전도)** — 등기부 벤더 계약이 끝난 뒤 착수 (3.1절)
7. 알림톡이 필요하면 템플릿 승인 리드타임(3~5영업일)을 미리 태우기

---

## 9. 자주 틀리는 지점 정리

| 함정 | 올바른 처리 |
|---|---|
| `.result` 를 벗겨서 `undefined` | 파트너 API는 **봉투가 없습니다**. 최상위가 곧 데이터입니다 |
| 금액 단위를 원으로 오해 | 전부 **만원**. 히트맵 `capPrice`만 **원** |
| `sdAdmissionRate`를 진학률로 표시 | **상위 백분위**입니다. 작을수록 상위 |
| 학교급 간 `admissionRate` 비교 | 정의가 달라 비교 불가 |
| 학교 거리를 도보 거리로 표기 | **직선거리**입니다 |
| 호재 반경 2km 조회 | 상한 **1km** |
| 히트맵 증감률을 실거래 기준으로 라벨링 | **호가** 기준입니다 |
| `null` 예측을 0으로 렌더 | 해당 블록을 감추세요 |
| `jeonsePrice: null`을 0으로 처리 | 역전세 판정 자체를 생략하세요 |
| 여러 서버가 각자 토큰 갱신 | 토큰은 하나만 유효. 공유 저장소에서 단일 갱신 |
| 시도명을 `서울특별시`로 전달 | 축약형 `서울` |

---

## 부록 A. 최소 동작 예제 (Node.js / TypeScript)

```ts
// server-only. 브라우저 번들에 포함되지 않도록 주의.
const BASE = "https://api-m.richgo.ai";

let cached: { token: string; expiresAt: number } | null = null;

async function getToken(): Promise<string> {
  if (cached && Date.now() < cached.expiresAt - 60_000) return cached.token;

  const res = await fetch(`${BASE}/v2/Authen/partner/token`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      apiKey: process.env.RICHGO_PARTNER_KEY,
      apiSecret: process.env.RICHGO_PARTNER_SECRET,
    }),
  });
  // 파트너 인증은 봉투 없이 최상위로 온다.
  const t = await res.json();
  cached = { token: t.accessToken, expiresAt: t.accessTokenExpiresAt };
  return t.accessToken;
}

/** 파트너 API 호출. 401이면 토큰을 한 번만 다시 받아 재시도한다. */
export async function richgo<T>(path: string, init: RequestInit = {}): Promise<T> {
  const call = async (token: string) =>
    fetch(`${BASE}/v2/Partner${path}`, {
      ...init,
      headers: { ...init.headers, Authorization: `Bearer ${token}` },
    });

  let res = await call(await getToken());
  if (res.status === 401) {
    cached = null;
    res = await call(await getToken());
  }
  if (res.status === 429) {
    const retryAfter = Number(res.headers.get("Retry-After") ?? 1);
    throw new Error(`RATE_LIMITED:${retryAfter}`);
  }
  if (!res.ok) throw new Error(`RICHGO_${res.status}`);

  // 파트너 API는 데이터를 최상위로 준다. 혹시 봉투가 씌워져 오면 그때만 벗긴다.
  const body = await res.json();
  return (body != null && typeof body === "object" && "result" in body
    ? (body as { result: T }).result
    : body) as T;
}

// 사용
// const ai = await richgo<AiPrice>("/danji/a9rJSEi/ai-price?pyeongType=33");
// const heat = await richgo<Heatmap>("/region/heatmap?sd=경북&sgg=포항시 북구");
```

## 부록 B. 테스트용 단지

| 단지 | danjiId | 지역 | 대표 평형 |
|---|---|---|---|
| 한화포레나포항 | `a9rJSEi` | 경북 포항시 북구 흥해읍 | 33 |

데모 화면의 모든 수치가 이 단지 기준입니다. 연동 검증에 쓰세요.
