한경 RPA 연동 가이드
얼마니 매물관리 × 한경부동산 RPA

한경 RPA 연동 가이드

중개사가 "한경에 반영" 버튼을 누르면 RPA가 한경부동산에 대신 등록해 주는 흐름을, API 명세서와 실제 코드를 대조해 처음 보는 사람도 따라갈 수 있게 풀어 쓴 문서입니다.

기준일 2026-09-04 원본 명세서 얼마니_기타 API 명세서.xlsx 18 시트 대조 대상 src/app/api/rpa/**, .../transfer/**

1. 등장인물은 넷, 브라우저는 둘

이 연동에서 가장 자주 헷갈리는 지점은 "브라우저"라는 말입니다. 브라우저가 두 개 있고, 각각 바라보는 사이트가 다릅니다.

중개사 브라우저

사람인 중개사가 자기 PC나 폰에서 얼마니 매물관리 사이트를 여는 브라우저. 매물을 등록하고 "한경에 반영" 버튼을 누르고 계정을 연동합니다. 얼마니 API만 부르며 RPA나 한경과는 직접 통신하지 않습니다.

얼마니 API 서버

Next.js 앱이 Vercel(icn1)에 올라가 있고 뒤에 PostgreSQL이 있습니다. 모든 요청의 종착지이자 큐(property_transfer_queue)의 주인입니다.

RPA 서버

얼마니 API를 주기적으로 물어보고, 할 일이 있으면 자기 브라우저를 띄워 한경 사이트를 조작하는 자동화 서버. 얼마니를 부를 때는 브라우저 없이 HTTP 요청으로 부릅니다.

한경부동산

매물이 실제로 올라가는 외부 사이트. API가 없어서 RPA가 브라우저로 로그인해 폼을 채웁니다. 등록이 끝나면 한경매물번호를 돌려줍니다.

이 문서의 표기. 이후 단계 목록에서 FE는 중개사 브라우저, 얼마니는 API 서버, RPA는 RPA 서버, 한경은 RPA가 띄운 브라우저가 조작하는 한경 사이트입니다.

2. 큰 그림: 얼마니는 RPA를 부르지 않는다

호출 방향은 두 가지뿐입니다. 중개사 브라우저가 얼마니를 부르고, RPA가 얼마니를 부릅니다. 얼마니가 RPA를 부르는 길은 설계상 없습니다. RPA가 "할 일 있나요?"를 반복해서 물어보는 pull 모델입니다.

중개사 브라우저 얼마니 매물관리 화면 얼마니 API 서버 Vercel + PostgreSQL 큐 RPA 서버 스케줄러 + 자동화 브라우저 한경부동산 외부 사이트 (API 없음) 세션 인증 큐 적재 · 상태 조회 Bearer 토큰 · 폴링 count → claim → credential → records 얼마니 → RPA 호출 없음 브라우저 자동화
RPA만 밖으로 요청을 보내므로 RPA 서버는 공인 주소나 열린 포트가 필요 없습니다.

그래서 "작업 시작"이라는 별도 신호는 없습니다. 중개사가 버튼을 누르면 얼마니 DB의 큐에 pending 행이 하나 생기고, RPA가 다음 폴링에서 그것을 발견하는 것이 곧 시작입니다. 기다리는 시간은 폴링 주기와 같습니다.

3. API 워크플로우

사이클은 세 개이고 서로 독립적으로 돕니다. 모든 RPA → 얼마니 호출에는 Authorization: Bearer <RPA_SERVICE_TOKEN>이 붙고, 대상 플랫폼은 현재 service=hankyung 하나입니다.

A. 전송 사이클 (등록 · 수정)

  1. A0
    FE얼마니POST /api/brokers/:brokerId/properties/:propertyId/transfer/queue명세서 미등재

    매물 수정 저장 후 "한경에도 반영할까요?" 모달, 또는 매물 목록의 개별 플랫폼 관리 모달에서 호출됩니다. 보내는 것은 { service, operation: 등록 | 수정 }.

    • 본인 확인, 계정 연동 여부, 매물 소유권, 필수값 사전 검증, 매체 상태(등록인데 이미 서비스중이면 거부) 를 통과하면 큐에 pending 으로 쌓입니다.
    • 같은 매물에 이미 활성 잡이 있으면 새로 넣지 않고 200 already_queued 로 알려 줍니다. 연타해도 중복 전송이 안 되는 이유입니다.
    • 같은 경로의 GET은 진행 중 잡을 돌려주어 화면이 '전송대기 / 전송중'을 표시합니다.
  2. A1
    RPA얼마니GET /api/rpa/transfer-queue/count?service=hankyung

    "지금 할 일이 몇 건인가?" 응답은 pending(대기 총건수)과 claimable(그중 계정 연동이 되어 지금 가져갈 수 있는 건수). RPA는 claimable을 기준으로 판단합니다. 인덱스를 타는 집계 쿼리 하나라 자주 불러도 가볍습니다.

  3. A2
    RPA얼마니POST /api/rpa/transfer-queue/claim

    보내는 것은 { service, limit 1~500 } 또는 단건 지정 { service, propertyId }. 서버는 대기 건을 원자적으로 선점processing으로 바꾸고 잡 목록을 돌려줍니다.

    • 잡 하나에는 queueId, brokerId, propertyId, operation(등록/수정), targetRef(수정 대상 한경매물번호), 그리고 한경 등록폼을 섹션별로 채운 detail이 들어 있습니다.
    • 로그인 정보는 여기 없습니다. 평문 비밀번호가 RPA 큐에 머물지 않게 하려는 의도입니다.
    • 매물 정보를 조립하다 필수값이 비어 있으면 그 건은 failed로 빠지고, 일시적 오류면 pending으로 되돌립니다. 그래서 claimed가 요청한 limit보다 작을 수 있습니다.
  4. A3
    RPA얼마니GET /api/brokers/:brokerId/credential?service=hankyung

    작업 직전에 해당 중개사의 한경 로그인 ID와 복호화된 비밀번호를 받습니다. 로그와 파일에 남기지 말고 즉시 쓰고 버리는 것이 계약입니다. 연동 계정이 없으면 404, 복호화 실패면 500이며 후자는 서버가 그 계정을 failed로 내립니다.

  5. A4
    RPA한경브라우저 자동화

    로그인 후 detail의 섹션(위치/구조, 가격, 매물정보, 입주일, 시설정보, 상세정보, 연락처 노출선택)을 폼에 옮겨 적습니다. 수정이면 targetRef로 기존 리스팅을 열어 덮어씁니다. 성공하면 한경매물번호를 얻습니다.

  6. A5
    RPA얼마니POST /api/brokers/:brokerId/properties/:propertyId/transfer/records

    결과 회신. { service, propertyType, propertyId, success, hankyungPropertyNo?, errors?: [{field, code}], registeredAt? }.

    • 한 트랜잭션으로 큐 행을 success/failed로 확정하고, transfer_response_history에 이력을 남기고, 성공이면 한경 매체 상태를 '서비스중'으로 만듭니다.
    • 실패면 Slack과 중개사 알림이 나갑니다. 성공이어도 errors를 함께 보낼 수 있고 서버는 그것을 경고로 보존합니다.
    • 응답의 queueUpdated가 false면 매칭되는 processing 잡이 없었다는 뜻입니다.

큐 한 행의 일생은 아래와 같습니다.

pendingclaim → processingrecords → success/failed

15분 리퍼. processing인 채 15분이 지나면 서버가 RPA가 죽은 것으로 보고 pending으로 되돌립니다(최대 5회, 넘으면 failed). 실제 작업이 15분을 넘기면 다른 워커가 같은 매물을 중복 등록할 수 있으니, 한 번에 가져가는 건수는 15분 안에 끝낼 만큼만 잡아야 합니다.

B. 상태변경 사이클 (거래완료 · 노출종료)

  1. B0
    FE얼마니매물 상태변경 API (PUT .../properties/:propertyId)

    중개사가 거래완료나 노출종료를 누르면 얼마니 자체 매체 상태는 즉시 바뀌고, 한경이 '서비스중'이면서 계정이 연동된 매물에 한해 큐에 상태변경 잡이 자동으로 들어갑니다. 별도 버튼이 없습니다.

  2. B1
    RPA얼마니GET /api/rpa/transfer-queue/status-change/count?service=hankyung

    A1과 같은 형식. 등록 큐와 분리되어 있어 서로의 재시도 횟수를 건드리지 않습니다.

  3. B2
    RPA얼마니POST /api/rpa/transfer-queue/status-change/claim

    잡에는 operation(거래완료/노출종료)과 targetRef(한경매물번호)만 있고 detail은 없습니다. 폼을 채우는 게 아니라 버튼을 누르는 일이기 때문입니다.

  4. B3
    RPA얼마니GET .../credential그리고RPA한경

    A3과 같은 방식으로 로그인 정보를 받아, targetRef로 매물을 찾아 상태변경 버튼을 누릅니다.

  5. B4
    RPA얼마니POST .../transfer/recordsA5와 같은 엔드포인트

    회신 경로는 등록과 같습니다. 서버는 큐 행의 operation을 보고 등록 결과인지 상태변경 결과인지 스스로 판단해 한경 매체 상태를 거래완료나 노출종료로 바꿉니다. 명세서 시트 제목이 "등록결과 저장"이라 상태변경 결과를 어디로 보내야 하는지 헷갈릴 수 있는데, 같은 곳입니다.

C. 계정 검증 사이클

  1. C0
    FE얼마니마이페이지 계정 연동 (.../platform-accounts/:service)

    중개사가 한경 ID/PW를 저장하면 암호화되어 보관되고 상태는 pending(인증 대기)이 됩니다.

  2. C1
    RPA얼마니POST /api/rpa/platform-accounts/claim

    { service, limit 1~50 }을 보내면 검증이 필요한 brokerId 목록만 옵니다. 전송 큐의 claim과 달리 잠금이 없어서 verify로 회신하기 전까지 매 폴링마다 같은 계정이 다시 나옵니다. 로그인 테스트는 다시 해도 되는 작업이라 의도된 설계입니다.

  3. C2
    RPA얼마니GET .../credential그리고RPA한경

    계정별로 로그인 정보를 받아 실제 로그인이 되는지 시험합니다. credential이 404면 그사이 연동이 해제된 것이니 건너뜁니다.

  4. C3
    RPA얼마니POST /api/rpa/platform-accounts/verify

    { service, brokerId, success, error? }. 성공이면 verified, 실패면 failed가 되고 중개사에게 '로그인 실패' 알림이 갑니다. failed 계정은 중개사가 비밀번호를 다시 입력해야 pending으로 돌아옵니다.

D. RPA와 무관한 시트

명세서의 "토지 실거래 주소 조회"와 "토지 실거래 pdf 업로드"는 api.ulmaniai.com의 다른 서버이고 인증 헤더도 다릅니다. "한경부동산_old", "전체 유형별", "매물별 필수값 정리" 시트는 API가 아니라 매물종류별 필드 매핑과 테스트 매물 목록입니다.

4. 용어 사전

이 문서와 두 작업 문서에 등장하는 낱말을 이 연동의 맥락에서 풉니다. 각 항목 끝의 초록 글씨는 그 개념이 실제로 어디에 있는지 가리킵니다.

queue
"해야 할 일"을 줄 세워 두는 표. 여기서는 PostgreSQL의 property_transfer_queue 테이블이며 한 행이 매물 하나 × 플랫폼 하나의 작업입니다. 누가 넣고(중개사), 누가 빼가는지(RPA)가 분리되어 있어 두 쪽이 서로의 속도에 묶이지 않습니다. docs/property-transfer-queue-migration.sql
폴링polling
일정 간격으로 "할 일 있나요?"를 반복해서 묻는 방식. RPA가 count를 주기적으로 부르는 것이 폴링입니다. 단순하고 RPA 쪽에 열린 포트가 필요 없다는 장점이 있고, 새 일감이 생겨도 다음 질문 때까지 기다린다는 것이 단점입니다. 그 대기 시간이 곧 폴링 주기입니다. GET /api/rpa/transfer-queue/count
스케줄러scheduler
"N초마다 이 함수를 실행해라"를 맡는 RPA 서버 안의 타이머. 폴링 주기는 얼마니 코드 어디에도 없고 이 스케줄러의 설정값입니다. 그래서 주기를 바꾸는 일은 얼마니 배포 없이 RPA 설정만으로 끝납니다.
claim선점
큐에서 일감을 집어 가면서 동시에 "내가 가져갔다"고 표시해 다른 워커가 못 가져가게 하는 것. 상태가 pending에서 processing으로 바뀝니다. 조회와 표시가 한 번에 일어나야 하며(원자적), 여기서는 PostgreSQL의 FOR UPDATE SKIP LOCKED로 구현되어 있어 워커가 여럿이어도 같은 행을 둘이 잡지 않습니다. src/app/api/rpa/transfer-queue/transferQueueCore.ts · claimQueue
claimablevs pending
pending은 대기 중인 전체 건수, claimable은 그중 중개사가 한경 계정을 연동해 두어서 실제로 로그인이 가능한 건수입니다. 미연동 건은 가져가도 로그인을 못 하므로 claim이 내보내지 않습니다. RPA는 claimable만 봅니다.
drain비우기
일감을 하나 발견하면 다음 주기를 기다리지 않고 claimed가 0이 될 때까지 곧바로 claim을 반복하는 것. 5건이 동시에 들어와도 한 사이클에 모두 처리되며, 주기를 줄이는 것과 함께 쓰면 체감 지연이 크게 줄어듭니다. 무한히 길어지지 않도록 한 번에 처리할 총 건수 상한을 둡니다. docs/RPA-폴링-주기-단축-작업계획.md §4-1
워커worker
실제로 한경 브라우저를 띄워 일하는 실행 단위. RPA 서버 한 대에 워커 하나일 수도, 여럿일 수도 있습니다. 워커 수가 늘어도 claim이 원자적이라 큐는 안전합니다.
backoff물러나기
호출이 실패했을 때(401, 5xx, 네트워크 오류) 곧바로 재시도하지 않고 간격을 2배씩 늘려 가며 물러나는 것. 장애 중인 서버를 더 두드리지 않고, 성공하면 즉시 원래 주기로 돌아옵니다. 401은 토큰 문제이므로 백오프와 함께 알림을 남깁니다.
jitter흔들기
주기에 ±몇 초의 난수를 섞어 여러 워커가 정확히 같은 순간에 서버를 두드리지 않게 하는 것. 워커가 한 대면 필요 없고, 여러 대일 때 요청이 한 초에 몰리는 것을 막습니다.
직렬화serialization
전송·상태변경·계정검증 세 사이클을 한 워커 안에서 동시에 돌리지 않고 차례로 돌리는 것. 브라우저가 하나인 워커에서 두 사이클이 동시에 한경 화면을 조작하면 서로 방해하기 때문입니다. 워커가 여럿이면 병렬로 돌려도 됩니다.
리퍼reaper
죽은 작업을 회수하는 청소부. processing인 채 15분이 지난 행을 RPA가 크래시한 것으로 보고 pending으로 되돌립니다. claim이 호출될 때 그 앞에서 실행됩니다. 재시도 5회를 넘기면 failed로 보냅니다. STALE_PROCESSING_MINUTES = 15, MAX_RETRY = 5
롱폴링long polling
"할 일 있나요?"에 서버가 바로 "없어요"라고 답하지 않고, 일감이 생기거나 제한 시간(예: 25초)이 지날 때까지 응답을 붙들고 있는 방식. RPA 쪽 인바운드 없이 수초 내 반응을 얻지만, 서버리스 함수가 대기 시간만큼 살아 있어야 해서 얼마니 쪽 비용이 늘어납니다. 이 문서의 C안입니다.
웹훅webhook · push
일이 생긴 쪽이 상대를 직접 호출해 알리는 방식. 얼마니가 큐에 넣은 직후 RPA의 URL을 부르는 것이 그 예입니다. RPA 서버가 HTTPS와 인증이 있는 공인 엔드포인트를 열어야 하고, 호출은 브라우저가 아니라 얼마니 API 서버가 해야 합니다. 이 문서의 B안입니다.
fire-and-forget
보내고 나서 응답을 기다리지 않는 호출. 웹훅을 쓴다면 이 방식이어야 RPA 서버가 죽어도 얼마니 API가 함께 느려지지 않습니다. 웹훅이 유실되면 폴링이 백업으로 잡아 줍니다.
Bearer 토큰RPA_SERVICE_TOKEN
RPA가 얼마니 API를 부를 때 붙이는 서비스용 비밀 문자열. 중개사의 로그인 세션과 별개이며 모든 RPA 엔드포인트에 필수입니다. 명세서 시트에 평문으로 들어 있어 플레이스홀더로 바꾸는 것이 권장됩니다.

5. 자주 나온 질문

"매물관리 클라이언트가 RPA에 '작업 시작하세요'라고 하는 API가 빠진 것 아닌가요?"

빠진 것이 아니라 형태가 다릅니다. RPA를 향해 명령하는 API 대신, 큐에 일감을 넣는 API(A0)가 이미 구현되어 있고 화면 두 곳에서 쓰고 있습니다. 명세서 엑셀에 이 API가 없어서 비어 보였을 뿐입니다. 지금 아무 작업을 하지 않아도 버튼 → 큐 → RPA 폴링 → 한경 등록 흐름은 끝까지 돌아갑니다.

"RPA 서버가 API를 만들고 매물관리 쪽이 요청하는 단방향은 어떤가요?"

RPA가 잡 데이터를 직접 받는 완전 push로 바꾸는 것은 권하지 않습니다. 지금 얼마니 쪽에 있는 원자적 선점, 재시도, 리퍼를 RPA에서 다시 만들어야 하고, RPA 서버가 공인 주소를 열어야 합니다. 굳이 얼마니 → RPA 호출을 두려면 "명령"이 아니라 "지금 큐 확인해 봐"라는 힌트로 제한하고, 잡의 진실은 큐에 남겨 두는 것이 안전합니다.

"중개사 브라우저가 RPA가 띄운 브라우저인가요?"

아닙니다. 중개사 브라우저는 사람이 얼마니 화면을 여는 브라우저이고, RPA 브라우저는 RPA 서버가 한경 사이트를 조작하기 위해 띄우는 별도의 브라우저입니다. 1장의 등장인물 표를 참고하세요.

"폴링 30초~1분이 기존 어떤 API인가요?"

새 API가 아닙니다. RPA가 기존 countclaim을 부르는 간격을 말합니다. 그 간격은 RPA 스케줄러 설정값이라 얼마니 코드는 바뀌지 않습니다.

"RPA 서버 부하를 줄이려면 A(짧은 폴링)보다 C(롱폴링)가 낫나요?"

아닙니다. 폴링 요청은 30초에 한 번이든 롱폴링이든 RPA 서버에 측정도 안 되는 부하입니다. RPA의 실제 부하는 브라우저로 한경 폼을 채우는 작업이고 그건 잡 개수만큼 생깁니다. 부하를 줄이는 손잡이는 claim의 limit, drain 상한, 워커당 브라우저 동시 실행 수입니다. C가 나은 점은 부하가 아니라 지연(수초)입니다.

6. 무엇이 어떻게 좋아지나

이번 세션에서 만든 두 문서가 현재 상태를 어떻게 바꾸는지 항목별로 정리합니다. 둘 다 얼마니 코드 변경은 없습니다. 하나는 문서와 명세의 정합을 맞추는 일이고, 하나는 RPA 설정을 바꾸는 일입니다.

6-1. 명세서 정합 · 갭 목록 docs/RPA-API-명세서-워크플로우-정리.md

엑셀 명세서를 실제 코드와 대조해 어긋난 곳과 없는 기능을 목록으로 만들었습니다.

항목AS-IS개선
큐 적재 API명세서에 없음. "시작 API가 없다"고 보이는 원인목차에 대분류 '얼마니'로 POST/GET transfer/queue 추가
상태변경 결과 회신시트 제목이 "등록결과 저장"뿐. RPA가 어디로 보낼지 불명확같은 엔드포인트가 상태변경 결과도 받는다고 명시
GET transfer?to=hankyung목차 비고 '완료'. 실제로는 폐기 예정'폐기 예정, claim.detail 사용'으로 표기
records 요청 예시응답 필드 queueUpdated가 요청 body에 들어 있음. 그대로 보내면 400예시에서 제거
registeredAtRequired=Trueoptional(서버 수신시각 폴백)로 정정
credential의 serviceRequest Body 표에 있음쿼리스트링·optional(기본 hankyung)로 이동
토큰·계정 예시dev 토큰과 실계정처럼 보이는 ID/PW가 평문플레이스홀더로 교체
없는 기능pending 취소 API, 진행 중 heartbeat, records의 queueId, 사진 전달이 없음우선순위와 제안 형태를 적어 후속 후보로 등재
시작 트리거 설계"클라이언트 → RPA 호출이 필요한가"가 미결A(짧은 폴링) / B(웹훅) / C(롱폴링) / D(메시지큐) 비교표와 권장 순서

6-2. 폴링 주기 단축 작업계획 docs/RPA-폴링-주기-단축-작업계획.md

A안을 실제로 적용하기 위한 작업 문서입니다. 핵심은 "기다리는 시간"을 줄이는 것이고, 그 부작용(리퍼 중복 등록, 서버 호출량)을 함께 막습니다.

항목AS-ISTO-BE
전송 count 주기5분 (핸드오프 문서 예시)30초
상태변경 count 주기5분60초
계정검증 claim 주기5분120초
잡 발견 후 다음 잡다음 주기까지 대기drain: claimed > 0이면 즉시 재claim, 총 건수 상한 있음
최대 대기 지연약 5분약 30초
호출 실패 시규칙 없음backoff: 2배씩 늘려 최대 5분, 성공 시 즉시 복귀, 401은 알림
워커 여러 대규칙 없음jitter ±5초로 요청 분산
세 사이클의 관계규칙 없음브라우저 1개 워커는 직렬화, 여러 대면 병렬 허용
15분 리퍼와의 충돌문서에 "15분 안에 끝낼 것"만한 건 처리 시간 × limit < 15분이 되도록 limit 산정 규칙
얼마니 서버 부하추정 없음워커 1대 월 약 15만 회 호출 추정, Vercel 한도·응답시간·커넥션 체크리스트
효과 측정방법 없음요청→선점 지연의 평균·p95 SQL, 중복 등록 감시 SQL, dev 시나리오 5개
롤백RPA 주기만 되돌리면 끝. 얼마니 배포 롤백 불필요

한 줄 요약. 기능은 이미 다 있습니다. 문서가 코드와 어긋나 "없어 보이는" 부분을 맞추고, RPA가 큐를 확인하는 간격을 5분에서 30초로 줄이면 중개사가 체감하는 반영 속도가 열 배 빨라집니다.

7. 다음 단계

  1. RPA 팀: 작업계획 §4의 루프(주기·drain·backoff·jitter·직렬화)를 스케줄러에 반영하고 dev에서 시나리오 5개를 확인합니다.
  2. 얼마니 팀: 핸드오프 문서의 "예: 5분" 세 곳과 엑셀 명세서를 §6-1 표대로 고칩니다. 적용 후 Vercel 호출량과 응답시간을 하루 지켜봅니다.
  3. 함께: 지연 측정 SQL로 적용 전후 p95를 비교합니다. 목표는 40초 이하입니다.
  4. 30초도 길다는 요구가 실제로 나오면 그때 C(롱폴링)를 검토합니다. 한 건 처리가 15분에 가까워지면 heartbeat API를 검토합니다.