API 문서
랭크프리 오픈 API — 인증 · 순위추적 · 경쟁분석 · 키워드분석 · 쇼핑 유입키워드 · 마케팅 상품 주문
랭크프리 기능을 업체 프로그램에서 바로 쓸 수 있는 API 입니다. 처음이면 시작하기 탭의 3단계부터 따라 하세요. 탭마다 API 별로 무엇을 하는지 · 주소 · 요청 칸 · 요청 예시 · 응답 예시 · 자주 나는 오류 순서로 적혀 있습니다.
플레이스 분석 scope: rank · compete · shop_keyword
네이버 플레이스(지도 업체)를 다루는 API 를 한곳에 모았습니다. 업체 찾기 → 상세정보 → 개별 분석 → 순위 확인 → 매일 순위 기록 → 경쟁 분석 순서로 쓰면 됩니다.
주소는 모두 https://rankfree.kr/api/v1 뒤에 붙이고, 모든 요청에 Authorization: Bearer YOUR_API_KEY 헤더를 넣습니다. 키에 해당 권한(scope)이 없으면 403 입니다.
| 메서드 | 주소 | 권한 | 하는 일 | 자세히 |
|---|---|---|---|---|
| GET | /rank/candidates | rank | 지역 + 업체명으로 플레이스 후보(ID·주소·전화·좌표) 찾기 | 순위추적 탭 |
| GET | /rank/search | rank | 검색어(지역 + 업종)로 나오는 플레이스 목록, 최대 300개 | 순위추적 탭 |
| GET | /rank/resolve | rank | 플레이스 주소를 업체 ID·이름 등으로 풀어 줌 | 순위추적 탭 |
| GET | /rank/place | rank | 업체 상세정보·메뉴·최근 방문자 후기·SNS·사진·위치 | 순위추적 탭 |
| GET | /rank/analysis | rank | 개별 분석: N1/N2/N3 점수·등급·정보 충실도·리뷰 신호·상위 업체 평균 | 순위추적 탭 |
| POST | /rank/check | rank | 슬롯 없이 지금 순위만 한 번 확인 | 순위추적 탭 |
| GET | /rank/slots | rank | 순위 추적 슬롯 목록 + 날짜별 순위 기록 + 사용량 | 순위추적 탭 |
| POST | /rank/slots | rank | 키워드 × 업체 순위 추적 등록(매일 자동 기록) | 순위추적 탭 |
| POST | /rank/slots/{id}/run | rank | 슬롯 순위를 지금 바로 다시 확인 | 순위추적 탭 |
| DELETE | /rank/slots/{id} | rank | 슬롯 삭제 | 순위추적 탭 |
| GET | /compete/tracks | compete | 경쟁 분석할 수 있는 내 슬롯 목록 | 경쟁분석 탭 |
| GET | /compete/{slotId} | compete | 내 업체와 같은 검색어 상위 업체 비교 결과 | 경쟁분석 탭 |
| POST | /compete/{slotId}/analyze | compete | 경쟁 분석 새로 돌리기 | 경쟁분석 탭 |
| POST | /place-rank/slots | shop_keyword | 업체 하나에 키워드 여러 개(최대 20)를 한 번에 순위 추적 등록 | 아래 |
| 하고 싶은 일 | 부르는 순서 |
|---|---|
| 업체 주소를 모를 때 | GET /rank/candidates(지역 + 업체명) → 주소로 지점 고르기 → place_id 저장 |
| 매장 진단 보고서 만들기 | GET /rank/place(상세·메뉴·후기) + GET /rank/analysis(점수·충실도·리뷰 신호) |
| 광고주 순위 매일 보고 | POST /rank/slots 또는 POST /place-rank/slots로 등록 → 매일 GET /rank/slots의 history 읽기 |
| 왜 순위가 밀렸는지 보기 | GET /compete/tracks → POST /compete/{slotId}/analyze → GET /compete/{slotId} |
| 상권(지역 업종) 조사 | GET /rank/search로 검색어별 업체 목록·좌표 받기 |
/place-rank/slots
업체 하나 × 키워드 여러 개 순위 추적 한 번에 등록
플레이스 하나에 키워드를 최대 20개까지 넣어 키 주인 계정의 순위 추적에 한 번에 등록합니다. 같은 플레이스 · 키워드 슬롯이 이미 있으면 새로 만들지 않고(exists), 꺼져 있던 슬롯은 다시 켭니다(reactivated). 새로 만들거나 다시 켠 슬롯은 그 자리에서 첫 순위를 한 번 확인합니다. 첫 확인이 실패해도 등록은 유지되고 매일 자동 수집이 이어받습니다. shop_keyword 권한이 필요하며 분당 60회까지입니다.
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
place | 필수 | 글자 | 플레이스 주소 또는 숫자 ID, 최대 1000자 |
keywords | 필수 | 배열 | 순위를 볼 검색어 1~20개, 각 최대 100자. 앞뒤 공백은 지우고 같은 검색어는 한 번만 등록 |
label | 선택 | 글자 | 슬롯 이름표, 최대 100자(예: 강남점) |
curl -X POST "https://rankfree.kr/api/v1/place-rank/slots" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"place":"https://m.place.naver.com/place/1234567890/home","keywords":["강남 미용실","강남역 미용실"],"label":"강남점"}'{
"slots": [
{"keyword": "강남 미용실", "status": "created", "slot_id": 501, "last_rank": 7, "checked_at": "2026-11-10T14:40:00+09:00"},
{"keyword": "강남역 미용실", "status": "exists", "slot_id": 377, "last_rank": 12, "checked_at": "2026-11-10T11:30:00+09:00"}
]
}| 필드 | 형식 | 설명 |
|---|---|---|
slots[].keyword | 글자 | 등록한 검색어 |
slots[].status | 글자 | created 새로 만듦 · reactivated 꺼진 슬롯 다시 켬 · exists 이미 추적 중 |
slots[].slot_id | 숫자 | 슬롯 번호. /rank/slots/{id}/run · /compete/{slotId}에 그대로 씀 |
slots[].last_rank | 숫자|null | 마지막 확인 순위. 규칙은 순위추적 탭의 rank 값과 같음(300 = 300위 밖, 0 = 조회 불가). 아직 확인 전이면 null |
slots[].checked_at | 글자|null | 마지막 확인 시각(ISO 8601) |
| 코드 | 뜻 |
|---|---|
| 401 / 403 | 키가 없거나 틀림 / 키에 shop_keyword 권한 없음 |
| 422 | 칸 형식 오류, 플레이스 주소를 알아볼 수 없음, 슬롯 한도 초과 등. message에 이유가 옵니다. 중간 키워드에서 422가 나면 앞 키워드 슬롯은 이미 등록돼 있으니 GET /rank/slots로 확인하세요 |
| 429 | 분당 60회 넘음. 잠시 뒤 다시 요청 |
첫 순위 확인 때문에 키워드 수에 따라 수 초~수십 초 걸릴 수 있습니다. 클라이언트 타임아웃을 넉넉히 두세요.
스토어 사업자 정보 (scope: store_bizinfo)
중복 없는 스토어 프로필 목록 조회 → 외부 수집기에서 공개 사업자 정보 확인 → 스토어별 POST 순서입니다. 회원과 키 모두 store_bizinfo 권한이 필요합니다. 콘솔 → API 키 → 권한에서 스토어 사업자 정보를 선택하세요. 기존 keyword_products 권한만으로는 사용할 수 없습니다. 회원 권한은 관리자가 부여합니다. 분당 60회와 키의 일일 한도가 적용됩니다.
서버가 네이버를 직접 수집하지 않습니다. 캡차가 나오면 중단하고 해당 스토어를 건너뛰세요. 풀기·우회·자동 클릭은 금지합니다. 공개 사업자 정보만 보내고 개인 휴대폰·비공개 이메일·원문 HTML·쿠키·토큰은 보내지 마세요. 프로필에 공개된 고객센터 번호는 휴대폰 형태여도 받은 그대로 저장합니다.
수집 상품에 store_id가 있는 스토어를 중복 없이 반환합니다. 최신 세부 정보의 상호·사업자번호·대표자·주소 중 하나가 없거나 captured_at이 stale_days일보다 오래된 경우 대상입니다. store_id가 없는 상품과 운영 중지로 표시한 스토어(status:"suspended")는 제외합니다. 각 스토어의 store_url, profile_url, store_name을 반환합니다. 브랜드스토어는 brand.naver.com, 스마트스토어는 smartstore.naver.com 프로필 주소입니다.
| 파라미터 | 설명 |
|---|---|
| limit | 1~100, 기본 20 |
| stale_days | 1~3650, 기본 30 |
| store_id | 선택: 특정 스토어만 확인 |
| after_store_id | 선택: 직전 응답 pagination.next_after_store_id. 스토어 ID 오름차순 커서. POST 후에도 page 번호를 쓰지 않아 대상 누락을 방지합니다. |
curl --get https://rankfree.kr/api/v1/stores/bizinfo -H "Authorization: Bearer <API_KEY>" -H "Accept: application/json" --data "limit=20" --data "stale_days=30"
curl --get https://rankfree.kr/api/v1/stores/bizinfo -H "Authorization: Bearer <API_KEY>" -H "Accept: application/json" --data "limit=20" --data "after_store_id=helinox" # 다음 쪽 curl --get https://rankfree.kr/api/v1/stores/bizinfo -H "Authorization: Bearer <API_KEY>" -H "Accept: application/json" --data "store_id=helinox" # 스토어 하나만
{"data":{"items":[{"store_id":"helinox","store_url":"https://brand.naver.com/helinox","profile_url":"https://brand.naver.com/helinox/profile","store_name":"헬리녹스","seller_info_id":null,"captured_at":null,"biz_name":null,"biz_reg_no":null,"representative":null,"address":null,"reason":"missing","missing_fields":["biz_name","biz_reg_no","representative","address"]}],"stale_days":30,"pagination":{"limit":20,"has_more":false,"next_after_store_id":null}}}
reason은 missing 또는 stale. 누락이 우선입니다. captured_at은 시간대가 포함된 ISO 8601 또는 null. 빈 목록도 200입니다. has_more=true면 커서를 그대로 사용하고, 완료 후 다음 수집 주기는 커서 없이 시작하세요. 조회 중 새로 생기거나 변경된 스토어는 다음 주기에서 확인하세요.
JSON 객체 한 건 또는 JSON 배열 1~100건으로 전송합니다. profile_url 또는 store_url로 스토어를 찾으며 기존 store_id 입력도 지원합니다. GET의 프로필 주소, 스토어 기본 주소, 모바일 스마트스토어 주소, 상품 주소를 모두 정규화합니다. 식별자를 여러 개 보내면 같은 스토어여야 합니다. 사업자번호·채널번호로 다른 스토어를 임의 연결하지 않습니다. 확인한 사업자 칸을 한 개 이상 보내세요(운영 중지만 알릴 때는 status만 보내도 됩니다). 생략·null·빈 문자열·공백만 있는 값은 기존 값을 보존합니다. 일부 칸만 저장할 수도 있으며, 기본 네 칸이 모두 채워지기 전에는 GET의 missing 대상에 남습니다. 같은 내용 재전송은 unchanged이며 변경 이력을 늘리지 않습니다. 새 관측 시각이면 내용이 같아도 수집 시각·출처만 갱신합니다. 실제 변경된 칸의 이전/이후 값·키 ID·회원 ID·관측 시각·공개 출처는 이력 테이블에 트랜잭션으로 기록됩니다.
| JSON 필드 | 검증 |
|---|---|
| profile_url / store_url / store_id | 셋 중 하나 필수. URL 최대 2000자. store_id는 영문·숫자·밑줄·하이픈, 최대 100자 |
| biz_name / representative / address | 선택, 받은 글을 그대로 저장(앞뒤 공백만 제거, 형식 검사 없음). 최대 1000 / 1000 / 2000자이고 넘으면 그 칸만 빼고 나머지를 저장(skipped_fields의 too_long) |
| biz_reg_no | 선택, 보낸 모양 그대로 저장(1234567890 · 123-45-67890 · 123 45 67890 모두 가능, 형식 검사 없음). 최대 255자이고 넘으면 그 칸만 빼고 저장 |
| mail_order_no / customer_phone / email | 선택, 모두 최대 1000자. 받은 글을 그대로 저장합니다(앞뒤 공백만 제거). 전화는 프로필에 공개된 값 그대로(휴대폰 형태도 가능), 이메일 칸에 앞뒤 글이 함께 있어도 그대로 저장합니다. 길이를 넘으면 그 칸만 빼고 저장 |
| source_url | 선택, 생략하면 정규화한 profile_url 사용. 공개 사업자 정보 출처 HTTPS URL, 최대 2000자. 사용자정보·쿼리·프래그먼트가 있으면 그 주소는 저장하지 않고 프로필 주소로 대신합니다. 서버는 이 URL을 요청하지 않습니다. |
| observed_at | 선택, 생략하면 API가 받은 시각 사용. 명시한 경우에만 오래된 관측 거절. 실제 확인 시각 ISO 8601(예: 2026-10-27T16:04:00+09:00). 형식이 틀리거나 미래면 받은 시각으로 대신합니다. 예시 시각을 그대로 재사용하지 마세요. |
| status | 선택, suspended(판매자 프로필에 「판매자의 사정에 따라 운영이 중지되었습니다.」가 나옴) 또는 active(다시 열림). 사업자 칸 없이 status만 보내도 받습니다. 그 밖의 값은 그 건만 invalid |
| message | 선택, status와 함께만. 화면에 나온 원문을 그대로, 최대 2000자. suspended일 때만 저장하고 active에서는 무시합니다. 넘으면 message만 빼고 status는 저장 |
| public_business_info | 선택, 보내면 true만 허용. 생략 여부와 관계없이 공개 사업자 정보만 전송 |
| 그 밖의 필드 | 정해지지 않은 필드는 버리지 않고 서버의 raw(JSON) 보관란 api_extra에 받은 그대로 저장합니다(같은 이름은 최신 값으로 바뀌고 changed_fields에 api_extra로 나옵니다). 사업자 칸과 함께 보낸 경우에만 저장하며(status만 보낸 요청은 보관할 곳이 없어 skipped_fields에 unknown_field로 알림), 한 필드가 JSON으로 2000자를 넘으면 그 필드만 too_long. 원문 HTML·개인 정보는 보내지 마세요. |
curl https://rankfree.kr/api/v1/stores/bizinfo -H "Authorization: Bearer <API_KEY>" -H "Accept: application/json" -H "Content-Type: application/json" --data '{"profile_url":"https://brand.naver.com/helinox/profile","biz_name":"예시 주식회사","representative":"예시 대표","biz_reg_no":"1234567890","address":"공개 사업장 주소"}'
{"data":{"store_id":"example","seller_info_id":1,"status":"updated","changed_fields":["address"],"captured_at":"2026-10-27T16:04:00+09:00","skipped_fields":[]}}
status: updated / unchanged (새 행도 updated). 예시 사업자 값은 설명용이며 운영 스토어에 그대로 보내지 마세요. 사업자번호는 형식도 검사하지 않고 받은 그대로 저장하며, 실제 등록 여부는 수집기가 공개 출처에서 확인해야 합니다. 권한을 가진 수집기가 공용 스토어 세부 정보를 갱신하므로 신뢰하는 수집기에만 권한을 부여하세요.
받은 값은 형식을 검사하지 않고 그대로 저장하며 거절하지 않습니다. 칸마다 정한 최대 길이를 넘는 칸(too_long)과 글이 아닌 값(invalid_format)만 그 칸만 빼고 나머지를 저장하며, 뺀 칸과 이유는 응답의 skipped_fields로 알려 드립니다. 저장할 수 있는 사업자 칸이 하나도 없을 때만 invalid(422)이고, 이때도 errors에 칸별 이유가 있습니다.
{"data":{"store_id":"example","seller_info_id":1,"status":"updated","changed_fields":["biz_name","customer_phone","api_extra"],"captured_at":"2026-10-27T17:50:00+09:00","skipped_fields":[{"field":"address","reason":"too_long"}]}}
배열 요청은 HTTP 200의 data.items에 입력 순서대로 index와 건별 결과 updated / unchanged / not_found / invalid를 반환합니다. 한 건의 검증 오류는 다른 건 저장을 취소하지 않습니다. 배열이 비었거나 100건을 넘으면 전체 요청은 422입니다.
[{"profile_url":"https://brand.naver.com/helinox/profile","biz_name":"예시 주식회사","representative":"예시 대표","biz_reg_no":"1234567890","address":"공개 사업장 주소"},{"store_url":"https://smartstore.naver.com/example","email":"office@example.com"}]
{"data":{"items":[{"index":0,"store_id":"helinox","seller_info_id":1,"status":"updated","changed_fields":["address"],"captured_at":"2026-10-27T16:40:00+09:00","skipped_fields":[]},{"index":1,"status":"not_found","code":"store_not_found","message":"Store not found in collected products."}]}}
운영 중지 스토어 제외 — 수집기가 프로필 주소를 열었을 때 「판매자의 사정에 따라 운영이 중지되었습니다.」가 나오면 사업자 칸 없이 status:"suspended"만 보내세요(message에 화면 원문을 그대로 넣으면 함께 기록). 이 스토어는 다음 GET 목록부터 빠집니다. 다시 열린 것을 확인하면 status:"active"를 보내세요. 목록에 돌아오고, 사업자 정보가 비었거나 오래됐으면 다시 수집 대상이 됩니다. 건별 결과의 status는 suspended 또는 active이고 changed는 처음 표시하거나 상태·원문이 바뀐 경우 true, 같은 내용을 다시 보내면 false입니다. 표시 시각(suspended_at)·원문(message)·해제 시각(released_at)은 서버에 기록되며 해제 뒤에도 마지막 중지 기록으로 남습니다. 관측 시각이 오래돼도 거절하지 않고 마지막으로 보낸 내용을 따릅니다. status와 사업자 칸을 함께 보내면 둘 다 저장하고 info_status(updated/unchanged)·changed_fields가 같이 옵니다.
curl https://rankfree.kr/api/v1/stores/bizinfo -H "Authorization: Bearer <API_KEY>" -H "Accept: application/json" -H "Content-Type: application/json" --data '{"profile_url":"https://brand.naver.com/helinox/profile","status":"suspended","message":"판매자의 사정에 따라 운영이 중지되었습니다."}'
{"data":{"store_id":"helinox","status":"suspended","changed":true,"suspended_at":"2026-10-27T18:10:00+09:00","released_at":null,"message":"판매자의 사정에 따라 운영이 중지되었습니다.","skipped_fields":[]}}
[{"profile_url":"https://brand.naver.com/helinox/profile","status":"suspended","message":"판매자의 사정에 따라 운영이 중지되었습니다."},{"store_url":"https://smartstore.naver.com/example","status":"active"}]
{"data":{"items":[{"index":0,"store_id":"helinox","status":"suspended","changed":true,"suspended_at":"2026-10-27T18:10:00+09:00","released_at":null,"message":"판매자의 사정에 따라 운영이 중지되었습니다.","skipped_fields":[]},{"index":1,"store_id":"example","status":"active","changed":true,"suspended_at":"2026-10-20T09:00:00+09:00","released_at":"2026-10-27T18:10:00+09:00","message":"판매자의 사정에 따라 운영이 중지되었습니다.","skipped_fields":[]}]}}
| HTTP / code | 처리 |
|---|---|
| 401 | 키 누락·무효·비활성·만료. 키 설정 확인 |
| 403 | 키 또는 회원 권한 부족·허용 IP 불일치 |
| 404 / store_not_found | 수집 상품에 없는 store_id. 목록 값을 확인 |
| 409 / stale_observation | 기존보다 오래된 관측 또는 같은 시각의 다른 값. 최신 공개 정보를 다시 확인 |
| 409 / store_identity_conflict | 채널이 다른 스토어와 연결됨. 관리자에게 매핑 확인 요청 |
| 422 | 스토어를 찾을 수 없는 주소·store_id 형식, public_business_info=false, status가 suspended/active가 아님, status 없이 message만 보냄, status도 저장할 수 있는 사업자 칸도 없는 경우. errors에 칸별 이유가 있고 서버 로그(shop_bizinfo_invalid)에도 남습니다. |
| 429 | 분당 또는 일일 한도 초과. 한도에 맞춰 재시도 |
시작하기 3단계
GET /products 로 상품 번호(id)를 찾고, GET /products/{id} 로 채워야 할 칸을 확인합니다.POST /orders 에 상품 번호와 키워드 · 주소 · 시작일 · 종료일 · 하루 수량을 보냅니다. 받은 주문번호로 GET /orders/{orderNo} 에서 상태를 봅니다. 자세한 칸은 마케팅 상품 주문 탭.# 1) 상품 목록
curl https://rankfree.kr/api/v1/products -H "Authorization: Bearer rk_..."
# 2) 주문 넣기(플레이스 예)
curl -X POST https://rankfree.kr/api/v1/orders \
-H "Authorization: Bearer rk_..." -H "Content-Type: application/json" \
-d '{"product_id": 2, "fields": {"keyword": "연남동 맛집", "place_url": "https://m.place.naver.com/restaurant/1234567890", "start_date": "2026-11-10", "end_date": "2026-11-16", "daily_qty": 100}}'
# 3) 상태 확인
curl https://rankfree.kr/api/v1/orders/MO2611061A2B3C -H "Authorization: Bearer rk_..."요금제
연간 계약 요금제입니다. 가격은 부가세 별도입니다. 호출 수는 한 계정의 모든 키를 합쳐 셉니다(하루는 한국시간 0시, 월은 1일에 다시 열림).
| 등급 | 연 가격 | 하루 호출 | 월 호출 | 쓸 수 있는 기능 | 키 수 | 지원 |
|---|---|---|---|---|---|---|
| 베이직 | 12,000,000원 | 10,000회 | 200,000회 | 순위추적 · 키워드분석 · 스토어 사업자 정보 · 마케팅 상품 주문 | 1개 | 이메일 문의 (영업일 1일 안 답변) |
| 프로 | 30,000,000원 | 50,000회 | 1,000,000회 | 순위추적 · 경쟁분석 · 키워드분석 · 키워드 상세분석 · 수집 상품 목록 · 스토어 사업자 정보 · 통합검색 쇼핑 순위 · 마케팅 상품 주문 | 3개 | 이메일 · 카카오톡 문의 (영업일 당일 답변) |
| 엔터프라이즈 | 50,000,000원 | 200,000회 | 5,000,000회 | 순위추적 · 경쟁분석 · 키워드분석 · 키워드 상세분석 · 수집 상품 목록 · 스토어 사업자 정보 · 통합검색 쇼핑 순위 · 마케팅 상품 주문 · 쇼핑 유입키워드 | 10개 | 전담 담당자 · 연동 기술 지원 |
문의 방법 — 고객센터에 원하는 요금제와 회사명을 남기면 담당자가 연락드립니다. 계약 뒤 관리자가 계정에 등급을 지정합니다.
상한을 넘으면 429, 등급에 없는 기능을 부르면 403 이 돌아옵니다.
# 하루 상한을 넘었을 때 (429)
{"message": "'베이직' 요금제의 하루 호출 수를 다 썼습니다. 내일 0시(한국시간)에 다시 열립니다.", "plan": "basic", "plan_daily_limit": 10000, "used_today": 10000}
# 등급에 없는 기능 (403)
{"message": "'베이직' 요금제에는 이 기능이 없습니다. 상위 요금제에서 쓸 수 있습니다.", "plan": "basic", "scope": "compete", "plan_scopes": ["rank", "keyword", "store_bizinfo", "order"]}인증
모든 요청에 Authorization: Bearer 헤더(또는 X-API-KEY)로 키를 전달합니다.
curl -H "Authorization: Bearer rk_xxxxxxxxxxxxxxxx" \
"https://rankfree.kr/api/v1/rank/slots"| 항목 | 값 |
|---|---|
| Base URL | https://rankfree.kr/api/v1 |
| 인증 헤더 | Authorization: Bearer rk_… 또는 X-API-KEY: rk_… |
| 응답 형식 | JSON (UTF-8) |
| 호출 한도 | 키에 설정한 일일 한도. 한도 설정 키는 응답에 X-RateLimit-Limit / X-RateLimit-Remaining 헤더 포함 |
오류 코드
| 코드 | 의미 |
|---|---|
401 | 키 없음/잘못됨 · 비활성화됨 · 유효기간 만료 |
403 | 허용되지 않은 IP · 키에 없는 권한(scope)의 엔드포인트 호출 · 요금제에 없는 기능 호출 · 요금제 기간 끝남 |
404 | 리소스 없음 (내 소유가 아닌 슬롯·주문 포함) |
422 | 요청 파라미터 검증 실패 |
429 | 키 일일 한도 초과 · 요금제 하루/월 호출 상한 초과 · 네이버 조회 일시 제한(blocked) |
순위추적 scope: rank
네이버 플레이스의 키워드 × 업체 순위를 조회하고, 슬롯으로 등록해 매일 자동 기록합니다.
슬롯을 만들면 일자별 순위 이력(history)이 쌓이고, 필요할 때 즉시 갱신(run)할 수 있습니다.
슬롯 없이 지금 순위만 확인하려면 POST /rank/check를 사용합니다.
| 공통 규칙 | 설명 |
|---|---|
| Base URL | https://rankfree.kr/api/v1 · 모든 요청에 Authorization: Bearer rk_… (또는 X-API-KEY). rank 권한이 없는 키는 403 |
rank 값 | 1 이상 = 노출 순위 · 300 = 상위 300위(6페이지) 밖 · 0 = 조회 불가(차단이거나 키워드·대상 플레이스 미확정). 300위 밖은 항상 300이며 0이 아닙니다 · blocked: true 이면 순위 미확정(rank는 0, 기록 저장 안 함) |
| 슬롯 한도 | used는 플레이스 + 쇼핑 순위추적 합산 사용량(활성 슬롯만 집계 — 자동 중단된 슬롯은 빠짐). limit이 -1 이면 무제한. 한도 초과 등록은 422 |
history | 이력을 함께 로드하는 응답(GET /rank/slots, run)에서만 배열이고, 슬롯 등록(POST /rank/slots) 응답에서는 null |
| 소요 시간 | run·check는 네이버 실시간 조회(최대 6페이지 · 페이지 간 지연)로 수 초~수십 초 걸립니다. 클라이언트 타임아웃을 넉넉히 두세요 |
| 소유권 | 내 소유가 아닌 슬롯 id로 run·DELETE 호출 시 403 |
/rank/slots
추적 슬롯 목록 + 사용량
내 계정의 플레이스 순위추적 슬롯을 최신 등록순으로 모두 반환합니다(자동 중단된 슬롯 포함). 각 슬롯에는 일자별 순위 이력(history, 날짜 오름차순)이 포함됩니다.
없음(인증 헤더만 필요).
curl https://rankfree.kr/api/v1/rank/slots \ -H "Authorization: Bearer rk_..."
{
"used": 12,
"limit": 100,
"slots": [
{
"id": 128,
"keyword": "강남 미용실",
"place_id": "1145161001",
"place_name": "라온헤어 강남점",
"place_url": "https://m.place.naver.com/place/1145161001/home",
"label": "강남점",
"category": "hairshop",
"last_rank": 3,
"last_review_count": 1240,
"last_checked_at": "2026-07-29T11:30:00+09:00",
"history": [
{"date": "2026-07-28", "rank": 4},
{"date": "2026-07-29", "rank": 3}
]
}
]
}| 필드 | 타입 | 설명 |
|---|---|---|
used | int | 활성 슬롯 수(플레이스 + 쇼핑 합산). 자동 중단된 슬롯은 세지 않으므로 slots[] 개수보다 작을 수 있습니다 |
limit | int | 등급별 슬롯 한도(추천 보너스 포함). -1=무제한 |
slots[] | array | 슬롯 목록(최근 등록순). 자동 중단된 슬롯도 포함되며, 활성 여부를 나타내는 필드는 현재 응답에 없습니다 |
slots[].id | int | 슬롯 ID — run·DELETE 경로에 사용 |
slots[].keyword | string | 추적 키워드 |
slots[].place_id | string|null | 네이버 플레이스 ID(숫자). 업체명으로만 등록한 경우 null |
slots[].place_name | string|null | 업체명(등록 시 자동 조회) |
slots[].place_url | string|null | 정규 모바일 플레이스 URL(m.place.naver.com/place/{id}/home) |
slots[].label | string|null | 등록 시 지정한 그룹 라벨 |
slots[].category | string | 업종 키(hairshop·restaurant·place 등). 판별하지 못했으면 place가 들어가며 null이 되지 않습니다 |
slots[].last_rank | int|null | 마지막 조회 순위. 아직 조회 전이면 null |
slots[].last_review_count | int|null | 마지막 조회 시점의 방문자 리뷰 수 |
slots[].last_checked_at | string|null | 마지막 확인 시각(ISO 8601, KST) |
slots[].history[] | array|null | 일자별 순위 기록(오름차순) |
slots[].history[].date | string | 기록 일자(YYYY-MM-DD) |
slots[].history[].rank | int | 그날의 순위 |
/rank/slots
슬롯 등록(키워드 다건)
플레이스 1곳에 키워드 N개를 한 번에 등록합니다. 업체명·업종·정규 URL은 서버가 자동 조회해 채웁니다. 이미 추적 중인 키워드는 생성하지 않고 skipped로 돌려줍니다. 등록만 하고 순위 조회는 하지 않으므로 last_rank는 null이며, 바로 순위를 얻으려면 이어서 run을 호출하세요.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
place | 필수 | string | 플레이스 URL 또는 ID(최대 1000자). m.place.naver.com·map.naver.com·naver.me 단축 URL·숫자 ID 모두 가능. ID를 못 찾고 URL도 아니면 입력값을 업체명으로 간주 |
keyword | 선택 | string | 단건 키워드(최대 100자). keywords가 없으면 필수 |
keywords | 선택 | array | 키워드 배열(1개 이상, 각 최대 100자). keyword가 없으면 필수. 두 값을 함께 보내면 합쳐서 처리(중복·공백 자동 제거) |
label | 선택 | string | 슬롯 그룹 라벨(최대 100자). 생성되는 모든 슬롯에 동일 적용 |
curl -X POST https://rankfree.kr/api/v1/rank/slots \
-H "Authorization: Bearer rk_..." \
-H "Content-Type: application/json" \
-d '{"place": "https://m.place.naver.com/hairshop/1145161001", "keywords": ["강남 미용실", "역삼 미용실"], "label": "강남점"}'HTTP/1.1 201 Created
{
"place": {
"place_id": "1145161001",
"place_name": "라온헤어 강남점",
"place_url": "https://m.place.naver.com/place/1145161001/home",
"category": "hairshop"
},
"created": [
{
"id": 128,
"keyword": "강남 미용실",
"place_id": "1145161001",
"place_name": "라온헤어 강남점",
"place_url": "https://m.place.naver.com/place/1145161001/home",
"label": "강남점",
"category": "hairshop",
"last_rank": null,
"last_review_count": null,
"last_checked_at": null,
"history": null
}
],
"skipped": ["역삼 미용실"]
}| 필드 | 타입 | 설명 |
|---|---|---|
place.place_id | string|null | 확정된 플레이스 ID. 못 찾으면 null |
place.place_name | string|null | 자동 조회한 업체명(ID를 못 찾고 URL도 아니면 입력값 그대로) |
place.place_url | string|null | 정규 모바일 플레이스 URL |
place.category | string|null | 업종 키. ID를 못 찾으면 null(이 경우 슬롯에는 place로 저장) |
created[] | array | 새로 만들어진 슬롯. 항목 구조는 GET /rank/slots의 slots[]와 동일하며 history는 null |
skipped[] | array | 이미 같은 키워드 × 플레이스로 추적 중이라 건너뛴 키워드 문자열 배열 |
키워드가 하나도 없거나 슬롯 한도를 넘기면 422와 함께 사유가 message에 담깁니다(예: 추적 한도(100개, 플레이스+쇼핑 합산)를 초과합니다. 현재 100개 사용 중 · 추가 가능 0개(요청 2개).). 한도 검사는 등록 전에 수행되므로 초과 시 슬롯이 하나도 생성되지 않습니다.
/rank/resolve
플레이스 메타만 조회
입력한 URL·ID·업체명에서 플레이스 ID를 확정하고 업체명·업종·정규 URL을 돌려줍니다. 슬롯을 만들지 않으므로 등록 화면의 미리보기(입력값이 올바른 플레이스인지 확인)에 사용하세요.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
place | 필수 | string | 플레이스 URL 또는 ID, 업체명(최대 1000자). 쿼리스트링으로 전달 |
curl -G "https://rankfree.kr/api/v1/rank/resolve" \ -H "Authorization: Bearer rk_..." \ --data-urlencode "place=https://m.place.naver.com/hairshop/1145161001"
{
"place": {
"place_id": "1145161001",
"place_name": "라온헤어 강남점",
"place_url": "https://m.place.naver.com/place/1145161001/home",
"category": "hairshop"
}
}| 필드 | 타입 | 설명 |
|---|---|---|
place.place_id | string|null | 확정된 플레이스 ID(숫자 문자열). 단축·딥링크 URL은 최종 URL까지 따라가 추출 |
place.place_name | string|null | 업체명. ID를 못 찾고 URL도 아니면 입력값을 그대로 업체명으로 반환 |
place.place_url | string|null | 정규 모바일 플레이스 URL. ID를 못 찾았고 입력이 URL이면 입력값 그대로, 아니면 null |
place.category | string|null | 업종 키(hairshop·restaurant 등). 판별 실패 시 place, ID를 못 찾으면 null |
/rank/slots/{id}/run
즉시 순위 갱신
슬롯의 순위를 지금 조회해 당일 기록을 저장(같은 날 기록이 있으면 덮어씀)하고, 슬롯의 최신값을 갱신합니다. 차단(blocked: true)이면서 미노출이면 기록을 남기지 않고 확인 시각만 갱신합니다. 조회 불가(rank: 0)가 3일 연속이면 해당 슬롯의 자동 추적이 중단됩니다(삭제 아님 — 콘솔에서 재개 가능). 기본 조회 경로에서 300위 밖은 rank: 300으로 기록되므로 순위 밖이 이어지는 것만으로는 중단되지 않습니다. rank: 0은 릴레이 폴백(nCaptcha 토큰 없이 릴레이가 설정된 경우) 결과가 0이거나, 슬롯의 place_id·place_name이 모두 비어 조회 자체가 불가능할 때만 기록됩니다.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
id | 필수 | int | 경로 파라미터 — 슬롯 ID. 내 소유가 아니면 403 |
curl -X POST https://rankfree.kr/api/v1/rank/slots/128/run \ -H "Authorization: Bearer rk_..."
{
"result": {
"blocked": false,
"found": true,
"rank": 3,
"list_total": 280,
"category": "hairshop",
"place_id": "1145161001",
"place_name": "라온헤어 강남점",
"review_count": 1240,
"blog_review_count": 320,
"save_count": 890,
"review_score": 4.9,
"tags": ["염색", "펌"]
},
"slot": {
"id": 128,
"keyword": "강남 미용실",
"place_id": "1145161001",
"place_name": "라온헤어 강남점",
"place_url": "https://m.place.naver.com/place/1145161001/home",
"label": "강남점",
"category": "hairshop",
"last_rank": 3,
"last_review_count": 1240,
"last_checked_at": "2026-07-29T11:32:07+09:00",
"history": [
{"date": "2026-07-28", "rank": 4},
{"date": "2026-07-29", "rank": 3}
]
}
}| 필드 | 타입 | 설명 |
|---|---|---|
result | object | 이번 조회 결과 — 구조는 POST /rank/check의 result와 동일 |
slot | object | 갱신된 슬롯(이력 포함) |
slot.id | int | 슬롯 ID |
slot.keyword | string | 추적 키워드 |
slot.place_id | string|null | 플레이스 ID. 비어 있었다면 이번 조회 결과로 채워짐 |
slot.place_name | string|null | 업체명. 비어 있었다면 이번 조회 결과로 채워짐 |
slot.place_url | string|null | 정규 모바일 플레이스 URL |
slot.label | string|null | 그룹 라벨 |
slot.category | string | 업종 키. 조회로 판별된 값으로 갱신되며 항상 값이 있습니다(판별 실패 시 place) |
slot.last_rank | int|null | 이번 조회 순위로 갱신 |
slot.last_review_count | int|null | 이번 조회 방문자 리뷰 수로 갱신 |
slot.last_checked_at | string|null | 확인 시각(ISO 8601, KST). 차단된 경우에도 갱신 |
slot.history[] | array | 일자별 순위 기록(date·rank, 오름차순). 이번 실행분 포함 |
/rank/slots/{id}
슬롯 삭제
슬롯을 삭제합니다. 삭제하면 해당 슬롯의 순위 이력도 함께 사라집니다. 활성 슬롯을 삭제하면 사용량(used)이 즉시 줄지만, 자동 중단된 슬롯은 애초에 used에 포함되지 않으므로 삭제해도 used는 그대로입니다. 되돌릴 수 없습니다.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
id | 필수 | int | 경로 파라미터 — 슬롯 ID. 내 소유가 아니면 403, 없는 ID면 404 |
curl -X DELETE https://rankfree.kr/api/v1/rank/slots/128 \ -H "Authorization: Bearer rk_..."
{
"deleted": true
}| 필드 | 타입 | 설명 |
|---|---|---|
deleted | bool | 삭제 완료 여부(항상 true) |
/rank/check
1회성 순위 조회
슬롯을 만들지 않고 키워드 × 플레이스 순위를 한 번만 조회합니다. 슬롯 한도를 소모하지 않고 기록도 남지 않습니다. 광고를 제외한 오가닉 순위이며 좌표는 서울 고정입니다. 업종 전용 리스트에서 미노출이면 통합 플레이스 리스트로 1회 다시 조회하는데, 이 폴백은 플레이스 ID를 확정했고 판별된 업종이 place가 아니며 차단되지 않은 경우에만 동작합니다. ID를 못 찾아 업체명 부분일치로 찾는 경우에는 폴백하지 않습니다.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
keyword | 필수 | string | 검색 키워드(최대 100자) |
place | 필수 | string | 플레이스 URL 또는 ID(최대 1000자). ID를 추출하지 못하면 입력값을 업체명으로 보고 리스트에서 부분일치로 찾습니다(이 경우 통합 리스트 폴백 없음) |
curl -X POST https://rankfree.kr/api/v1/rank/check \
-H "Authorization: Bearer rk_..." \
-H "Content-Type: application/json" \
-d '{"keyword": "강남 미용실", "place": "https://m.place.naver.com/hairshop/1145161001"}'{
"result": {
"blocked": false,
"found": true,
"rank": 3,
"list_total": 280,
"category": "hairshop",
"place_id": "1145161001",
"place_name": "라온헤어 강남점",
"review_count": 1240,
"blog_review_count": 320,
"save_count": 890,
"review_score": 4.9,
"tags": ["염색", "펌"]
}
}| 필드 | 타입 | 설명 |
|---|---|---|
result.blocked | bool | 네이버 조회 차단(429/405 · 토큰 만료) 여부. true면 순위는 신뢰할 수 없으니 잠시 뒤 재시도 |
result.found | bool | 상위 300위 리스트에서 대상 업체를 찾았는지 여부 |
result.rank | int | 순위(1~). 300=리스트 내 미노출(300위 밖은 언제나 이 값), 0=차단(blocked: true)이거나 조회 불가(키워드·대상 미확정 · 릴레이 폴백 결과 0) |
result.list_total | int | 해당 키워드 검색 결과의 총 업체 수 |
result.category | string | 조회에 사용한 업종 키(hairshop·restaurant·place 등) |
result.place_id | string | 매칭된 플레이스 ID. 못 찾으면 요청에서 확정한 ID 또는 빈 문자열 |
result.place_name | string | 매칭된 업체명. 못 찾으면 빈 문자열 |
result.review_count | int|null | 방문자 리뷰 수 |
result.blog_review_count | int|null | 블로그·카페 리뷰 수 |
result.save_count | int|null | 저장 수 |
result.review_score | float|null | 방문자 리뷰 평점 |
result.tags | array | 업체 대표 태그(문자열 배열). 없으면 빈 배열 |
/rank/search키워드별 플레이스 목록 · 최대 300개| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
keyword | 필수 | 글자 | 지역 + 검색어, 최대 100자(예: 서울 마포구 망원동 소금빵) |
curl -G "https://rankfree.kr/api/v1/rank/search" \ -H "Authorization: Bearer rk_..." \ --data-urlencode "keyword=서울 마포구 망원동 소금빵"
keyword에 지역과 검색어를 전달합니다(예: 서울 마포구 망원동 소금빵). rank 권한이 필요하며 고정 분당 호출 제한은 없습니다. 응답은 total(전체 결과 수), count(수집 수), items(place_id/name/address/road_address/category/x/y), capped(300개 초과), partial, blocked입니다. 실제 검색 결과가 5개면 5개, 100개면 100개를 반환합니다. 차단 시 429와 이미 수집한 결과를 함께 반환할 수 있습니다.
/rank/candidates플레이스 URL 찾기 · 지역 + 업체명 후보 검색플레이스 URL을 모를 때 신규개업 업체 찾기와 같은 상호 검색 경로로 후보를 조회합니다. keyword에 조합 검색어를 넣거나, name과 선택값 region을 전달하세요. keyword와 name은 함께 사용할 수 없습니다. rank 권한이 필요하며 고정 분당 호출 제한은 없습니다.
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
name | 선택 | 글자 | 업체명, 최대 100자. name · keyword 중 하나는 꼭 보냄 |
region | 선택 | 글자 | 지역(예: 제주 애월). name 과 함께 |
keyword | 선택 | 글자 | 지역+업체명을 한 줄로(예: 제주 애월 랜디스도넛). name 과 같이 쓰면 422 |
limit | 선택 | 숫자 | 1~50, 기본 20 |
curl -G "https://rankfree.kr/api/v1/rank/candidates" \ -H "Authorization: Bearer rk_..." \ --data-urlencode "region=제주 애월" \ --data-urlencode "name=랜디스도넛" \ --data-urlencode "limit=20"
curl -G "https://rankfree.kr/api/v1/rank/candidates" \ -H "Authorization: Bearer rk_..." \ --data-urlencode "keyword=제주 애월 랜디스도넛"
limit은 1~50(기본 20), 검색 첫 페이지의 공개 후보만 반환하며 페이지네이션은 지원하지 않습니다.
| 필드 | 설명 |
|---|---|
| query / status / total / count / has_more | 실제 검색어, 상태(ok/partial/blocked/unavailable), 전체 검색 건수, 반환 건수, 더 많은 결과 존재 여부. 실패 시 total은 null이며 검색 결과 0건과 구별합니다. |
| candidates[] | place_id, place_url, name, category, address, road_address, common_address, phone, latitude, longitude. 원본에 없는 값은 null. road_address는 축약 주소일 수 있어 common_address와 함께 확인하세요. |
후보를 자동 확정하지 않습니다. 업체명과 주소로 원하는 지점을 고른 뒤 GET /rank/place?place=선택한_place_id로 상세정보·메뉴·후기·SNS·이미지를 조회하세요. 검색 결과가 없으면 HTTP 200과 빈 배열, 원본 차단은 429, 통신·파싱 실패는 503입니다.
/rank/analysis플레이스 개별 분석 · N1/N2/N3 · 정보 충실도 · 리뷰 신호콘솔의 플레이스 개별 분석과 같은 계산기를 사용합니다. 필수값 place(URL 또는 ID), keyword(분석 기준 검색어), 선택값 category(restaurant/place 등). rank 권한으로 사용하며 추적 슬롯을 만들지 않습니다.
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
place | 필수 | 글자 | 플레이스 주소 또는 숫자 ID, 최대 1000자 |
keyword | 필수 | 글자 | 분석 기준 검색어, 최대 100자 |
category | 선택 | 글자 | 업종: place · restaurant · hairshop · nailshop · hospital · accommodation |
curl -G "https://rankfree.kr/api/v1/rank/analysis" \ -H "Authorization: Bearer rk_..." \ --data-urlencode "place=https://m.place.naver.com/restaurant/1000671392/home" \ --data-urlencode "keyword=애월 도넛" \ --data-urlencode "category=restaurant"
| 필드 | 설명 |
|---|---|
| analysis.n1 / n2 / n3 / tier / rank | 유사도·관련성·랭킹 추정 점수, 등급, 검색 순위. 네이버 공식 점수가 아닌 랭크프리 자체 추정치입니다. rank=300은 기존 분석기의 300위 이상/미확인 표시입니다. |
| analysis.d | d1 방문자 리뷰, d2 블로그 리뷰, d3 예약 리뷰, d4 평점, d5 저장수, d6 사진수, d7 정보 충실도, d8 키워드 일치, d9 최근 활동, d10 리뷰 영향력. |
| analysis.seo / kc / rep_keywords | 정보 충실도 체크리스트, 키워드 일치 근거, 대표 키워드. |
| analysis.review_kw / review_quality / review_weekly | 리뷰 키워드·작성자 영향력/방문 맥락·최근 주별 표본 집계. 긍정·부정 감성 분석은 포함하지 않으며 review_sentiment_status=not_analyzed로 표시합니다. |
| analysis.benchmark | 분석 검색어의 상위 업체 평균 집계. 개별 경쟁업체·주변매장 목록은 반환하지 않습니다. |
| cached / analyzed_at / cache_seconds | 동일 플레이스·키워드·업종 결과는 6시간 캐싱합니다. 분석 시각을 확인하세요. |
첫 분석은 수십 초 이상 걸릴 수 있어 클라이언트 타임아웃 300초를 권장합니다. 동일 분석 진행 중 409(Retry-After), 수집 실패 503. 다른 키워드의 점수는 비교 모집단이 달라 직접적인 지역 순위로 해석하지 마세요. 디저트나우 추천·후기 점수는 이 API에 포함되지 않습니다.
/rank/place
업체정보 · 메뉴 · 최근 후기 · 위치/좌표
플레이스 상세 정보를 한 번에 조회합니다. 기존 rank 권한으로 사용하며 키워드나 추적 슬롯은 필요하지 않습니다. 순위는 POST /rank/check로 조회하세요. 홈·메뉴·최신 방문자 리뷰 페이지의 공개 데이터를 수집하며, 전체 메뉴/리뷰 이력 수집이나 페이지네이션은 제공하지 않습니다. 요청당 최대 3페이지를 조회하므로 클라이언트 타임아웃은 90초 이상을 권장합니다.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
place | 필수 | string | 숫자 ID 또는 네이버 플레이스 URL(최대 1000자). m.place.naver.com·pcmap.place.naver.com·map.naver.com·naver.me 지원. 업체명만 입력하면 422 |
review_limit | 선택 | int | 최근 방문자 후기 최대 개수, 1~50(기본 10). 공개 페이지에서 수집된 수만큼만 반환 |
cache_only | 선택 | bool | 1이면 앱 공통 저장본만 조회합니다. 네이버 재수집 없음. 없으면 404. 숫자 ID 또는 ID가 포함된 정규 URL 사용(단축 URL은 422). |
refresh | 선택 | bool | 1이면 네이버에서 새로 수집하고 공통 저장본을 갱신합니다. cache_only=1과 함께 사용하면 422. |
curl -G "https://rankfree.kr/api/v1/rank/place" \ -H "Authorization: Bearer rk_..." \ --data-urlencode "place=https://map.naver.com/p/entry/place/123456" \ --data-urlencode "review_limit=10"
기본 동작은 공통 저장본 우선입니다. K-Food Map·디저트나우에서 수집한 상세를 플레이스 ID 기준으로 재사용하며 저장본이 없거나 일부 섹션이 수집되지 않았을 때 새로 수집합니다. cache_only=1이면 불완전한 저장본도 상태 표시와 함께 그대로 반환합니다. 기존 앱 저장본은 약 1분 주기로 동기화합니다. 오래된 저장본도 자동 재수집하지 않으므로 cache.stale과 수집 시각을 확인하고 필요할 때 refresh=1을 요청하세요. 앱별 할랄·비건 검증, 공개 여부, 추천 점수는 공유하지 않습니다.
cache.hit: 저장본 사용 여부, cache.section_collected_at: 업체·메뉴·후기별 정상 수집 시각(UTC), cache.stale: 정상 섹션 중 24시간 이상 지난 자료 포함 여부. cached_images.photo_url/photos: 저장된 320/640px WebP 캐시 URL(수집된 경우). 원본 사진은 place.images에 유지됩니다. 앱은 사진 캐시 URL을 재사용할 수 있습니다.
재수집 없는 호출 예: GET /api/v1/rank/place?place=123456&cache_only=1&review_limit=50. 새로 수집하는 호출 예: GET /api/v1/rank/place?place=123456&refresh=1. 기존 Bearer API 키와 rank 권한이 동일하게 적용됩니다.
{
"place": {
"place_id": "123456",
"place_url": "https://m.place.naver.com/place/123456/home",
"name": "예시 식당", "category": "한식", "phone": "02-123-4567",
"description": "매장 소개", "business_hours": null,
"conveniences": [], "payment_info": [],
"location": {
"address": "서울 강남구 역삼동", "road_address": "서울 강남구 테헤란로 1",
"directions": "1번 출구", "latitude": 37.5, "longitude": 127.01
},
"review_count": 20, "blog_review_count": 5,
"menus": [{"id": "1", "name": "정식", "price": "12,000", "description": "대표메뉴", "images": []}],
"recent_reviews": [{"id": "r1", "body": "맛있어요", "created_at": "2026-08-02", "visited_at": null, "rating": null, "author": "방문자", "media": []}]
},
"status": {"business": "ok", "menus": "ok", "recent_reviews": "ok"},
"partial": false,
"fetched_at": "2026-08-02T12:00:00+09:00"
}| 필드 | 타입 | 설명 |
|---|---|---|
place | object | 업체 ID·정규 URL·상호·한글 업종·전화·소개·영업시간·편의시설·결제정보·방문자/블로그 리뷰 수. 원문에 없는 값은 null 또는 빈 배열. business_hours·conveniences·payment_info는 참조를 해제한 네이버 원문 구조 |
place.location | object | 지번/도로명 주소·찾아오는 길·latitude(위도)·longitude(경도). 좌표는 업체의 실제 좌표이며 누락 시 null |
place.menus[] | array | 메뉴 ID·이름·표시 가격 문자열·설명·이미지. 가격은 "변동" 등 문자가 포함될 수 있음. 메뉴 페이지 실패 시 홈에 포함된 메뉴만 반환할 수 있음 |
place.links[] / images[] | array | 공식 홈페이지·SNS 링크(type/url), 업체 이미지(url/width/height). 이미지 원본 URL은 별도 축소·캐싱하여 사용할 수 있습니다. |
place.transportation | object | directions(찾아가는 길), route_url, subway_stations(지하철역), bus_stations(정류장·노선·도보 거리/시간), parking(주차·발레·요금 안내). 원본에 공개된 범위만 반환합니다. |
place.additional_info | object | 시설·반려동물·노키즈존·업체 키워드·예약/주문·인증·방송 등 해당 업체의 공개 부가정보. 주변매장·추천업체·광고는 제외합니다. |
place.recent_reviews[] | array | 최신 방문자 후기: id, body(내용), created_at(작성일 원문), visited_at(방문일 원문), rating(평점), author(공개 닉네임), media(공개 type/url/thumbnail). 작성일 내림차순, 최대 review_limit개 |
status | object | business·menus·recent_reviews별 수집 상태. ok=페이지 데이터 파싱 성공(목록이 없을 수 있음), blocked=네이버 차단, unavailable=통신/파싱 실패 또는 대상 업체 정보 없음 |
partial | bool | 하나 이상의 페이지 조회 실패. 메뉴/리뷰만 실패하면 HTTP 200으로 확인된 정보를 반환하며, 빈 배열만으로 데이터가 없다고 단정하지 말고 status를 확인 |
fetched_at | string | 수집 완료 시각(ISO 8601) |
업체 홈 차단은 HTTP 429, 홈 조회/파싱 실패는 503입니다. 잘못된 입력은 422, 인증/권한 오류는 401/403입니다. 고정 분당 호출 제한은 없습니다. 키별 일일 한도 설정과 원본 사이트의 차단 응답은 별도로 적용됩니다. 슬롯 생성·순위 기록 저장은 하지 않습니다.
경쟁분석 scope: compete
순위추적 슬롯(키워드 x 플레이스)을 기준으로 네이버 플레이스 상위 노출 경쟁 업체를 수집해, 내 플레이스와 리뷰·평점·사진·정보충실성 등의 신호를 비교·점수화합니다.
분석 실행(POST)은 수집을 동기로 처리해 수십 초가 걸리며, 결과는 하루 1건의 일자별 스냅샷으로 저장되어 이후 조회(GET)로 언제든 다시 읽을 수 있습니다.
점수 N1/N2/N3·D1~D10 은 랭크프리가 관측 신호로 산출한 자체 추정치이며 네이버가 공개하는 공식 지표가 아닙니다.
| 항목 | 규칙 |
|---|---|
| 인증 | Authorization: Bearer rk_... 헤더(또는 X-API-KEY). compete 권한이 있는 API 키만 호출할 수 있습니다 |
| Base URL | https://rankfree.kr/api/v1 |
slotId | 순위추적 슬롯 ID. GET /compete/tracks 응답의 slot_id 를 사용합니다. 존재하지 않는 ID 면 404, 존재하지만 본인 소유가 아니면 403 |
| 점수 성격 | N1/N2/N3·D1~D10 은 랭크프리 자체 추정치(0~100). 네이버 공식 지표가 아니며 같은 키워드 경쟁셋 안에서의 상대 비교용입니다 |
| 점수 직렬화 | DB 에 저장된 점수(d1~d10·n1~n3)와 review_score 는 소수 자릿수가 고정된 문자열로 내려옵니다 — "82.500"(점수, 소수 3자리) · "4.87"(평점, 소수 2자리). 분석 실행(POST) 응답의 my_score 만 계산 직후 값이라 숫자(float)입니다. 클라이언트에서 숫자로 변환해 사용하세요 |
| 분석 단위 | 날짜(ymd, Asia/Seoul) 기준 스냅샷. 같은 날 다시 실행하면 그날 스냅샷을 덮어씁니다 |
| 순위 표기 | 상위 300위(6페이지)까지 탐색. 그 안에서 찾지 못하면 my_rank 와 저장되는 rnk 가 모두 300 입니다(0 은 내려가지 않습니다). 슬롯에 플레이스가 지정되지 않은 경우에도 my_rank 는 300 |
| 오류 | 401 키 무효·만료 / 403 scope 없음·타인 슬롯 / 404 존재하지 않는 slotId(경로의 슬롯은 라우트 모델 바인딩이라 소유 검사보다 먼저 404 가 납니다) / 429 일일 호출 한도 초과 또는 네이버 조회 차단 |
/compete/tracks
슬롯별 최신 분석 요약
내 순위추적 슬롯 전체와 각 슬롯의 가장 최근 분석 결과(N1/N2/N3·순위)를 한 번에 가져옵니다. 요청 파라미터는 없습니다. 아직 한 번도 분석하지 않은 슬롯은 analyzed_ymd 와 점수 필드가 모두 null 로 내려옵니다. 다른 엔드포인트에 쓰는 slotId 는 여기의 slot_id 값입니다.
curl https://rankfree.kr/api/v1/compete/tracks \ -H "Authorization: Bearer rk_..."
{
"tracks": [
{
"slot_id": 12,
"keyword": "강남 미용실",
"place_id": "1145161001",
"place_name": "라온헤어 강남점",
"category": "hairshop",
"analyzed_ymd": "2026-07-29",
"n1": "82.500",
"n2": "74.132",
"n3": "68.041",
"rnk": 3
},
{
"slot_id": 15,
"keyword": "역삼동 네일샵",
"place_id": "1029384756",
"place_name": "뷰티네일 역삼점",
"category": "nailshop",
"analyzed_ymd": null,
"n1": null,
"n2": null,
"n3": null,
"rnk": null
}
]
}| 필드 | 타입 | 설명 |
|---|---|---|
tracks[] | array | 순위추적 슬롯 목록(최근 생성 순) |
tracks[].slot_id | int | 슬롯 ID — 이후 호출의 slotId |
tracks[].keyword | string | 추적 키워드 |
tracks[].place_id | string | 네이버 플레이스 ID |
tracks[].place_name | string | 업체명 |
tracks[].category | string | 업종 경로 — place·restaurant·hairshop·nailshop·hospital·accommodation |
tracks[].analyzed_ymd | string|null | 최신 분석일(YYYY-MM-DD). 분석 이력이 없으면 null |
tracks[].n1 | string|null | 키워드 일치 점수(0~100, 자체 추정치). 저장값 그대로라 소수 3자리 문자열 |
tracks[].n2 | string|null | 경쟁력 종합 점수(0~100, 자체 추정치). 소수 3자리 문자열 |
tracks[].n3 | string|null | 순위 환산 점수(0~100, 자체 추정치). 소수 3자리 문자열 |
tracks[].rnk | int|null | 최신 분석 시점의 내 플레이스 순위. 300 = 상위 300위 밖(미노출) |
/compete/{slotId}
최신 분석 상세(경쟁셋·해설·추이)
해당 슬롯의 가장 최근 분석일 스냅샷을 반환합니다. 경쟁셋 비교표(rows), 내 플레이스 점수 해설(explain), 일자별 추이(series)로 구성됩니다. rows 는 내 플레이스가 맨 앞에 오고 그다음 순위 오름차순입니다. 분석 이력이 없으면 analyzed: false 로만 응답하므로, 먼저 POST /compete/{slotId}/analyze 를 실행해야 합니다.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
slotId | 필수 | int | 경로 파라미터. 순위추적 슬롯 ID(없는 ID 는 404, 본인 소유가 아니면 403) |
curl https://rankfree.kr/api/v1/compete/12 \ -H "Authorization: Bearer rk_..."
{
"analyzed": true,
"ymd": "2026-07-29",
"keyword": "강남 미용실",
"place_name": "라온헤어 강남점",
"rows": [
{
"rnk": 3,
"name": "라온헤어 강남점",
"place_id": "1145161001",
"is_mine": true,
"visitor_cnt": 1842,
"blog_cnt": 407,
"review_score": "4.87",
"d7": "78.400",
"n1": "82.500",
"n2": "74.132",
"n3": "68.041",
"tier": 2
},
{
"rnk": 1,
"name": "제로헤어 강남본점",
"place_id": "1038472910",
"is_mine": false,
"visitor_cnt": 3120,
"blog_cnt": 655,
"review_score": "4.92",
"d7": "91.200",
"n1": "88.000",
"n2": "86.417",
"n3": "100.000",
"tier": 2
}
],
"explain": {
"components": {
"L": 1.0,
"B": 1.0,
"T": 0.6,
"M": 1.0,
"region": "강남",
"core": "강남",
"bizterm": "미용실"
},
"seo": [
{
"label": "메뉴/시술",
"raw": "24개",
"grade": 1.0,
"w": 1.5,
"avail": 1
},
{
"label": "대표키워드",
"raw": "5개",
"grade": 1.0,
"w": 1.5,
"avail": 1
},
{
"label": "필수정보 완성",
"raw": "찾아오는길 누락",
"grade": 0.67,
"w": 1.0,
"avail": 1
}
],
"dims": {
"d1": "88.240",
"d2": "71.503",
"d3": "64.118",
"d4": "82.667",
"d5": null,
"d6": "59.420",
"d7": "78.400",
"d8": "82.500",
"d9": "66.315",
"d10": "73.333",
"n1": "82.500",
"n2": "74.132",
"n3": "68.041"
},
"review_kw": {
"menus": [
{ "l": "뿌리염색", "c": 32 },
{ "l": "레이어드컷", "c": 21 }
],
"themes": [
{ "l": "친절해요", "c": 51 }
],
"voted": [
{ "l": "상담이 자세해요", "c": 27 }
]
},
"review_weekly": {
"v": [12, 21, 33, 46],
"b": [3, 5, 8, 11]
},
"review_quality": {
"photo_n": 18,
"photo_total": 31,
"photo_ratio": 0.581,
"ctx": {
"예약 후 이용": 14,
"지인 추천": 6
},
"authority": {
"infl": 5,
"hi_infl": 3,
"power": 2,
"avg_fol": 63,
"top": [
{ "n": "라라뷰티", "f": 1820, "r": 214, "rt": 5 },
{ "n": "강남헤어러버", "f": 940, "r": 88, "rt": 4.5 }
]
},
"bloggers": [
{ "id": "beauty_haru", "n": "하루의 미용일지" }
]
}
},
"series": [
{
"ymd": "2026-07-27",
"n1": "82.500",
"n2": "71.204",
"n3": "65.238",
"rnk": 5
},
{
"ymd": "2026-07-29",
"n1": "82.500",
"n2": "74.132",
"n3": "68.041",
"rnk": 3
}
]
}{
"analyzed": false,
"rows": [],
"mine": null,
"explain": null
}| 필드 | 타입 | 설명 |
|---|---|---|
analyzed | bool | 분석 이력 유무. false 면 rows 는 빈 배열, mine·explain 은 null 만 내려옵니다 |
ymd | string | 스냅샷 기준일(YYYY-MM-DD) — 가장 최근 분석일 |
keyword | string | 슬롯 키워드 |
place_name | string | 내 플레이스 업체명 |
rows[] | array | 해당 일자 경쟁셋 — 상위 30개. 내 플레이스가 상위 30 밖이면 내 플레이스 행이 따로 한 건 더 저장되어 최대 31행이 됩니다. 내 플레이스 우선 → 순위 오름차순 |
rows[].rnk | int | 노출 순위(1~). 300 = 상위 300위 밖 |
rows[].name | string | 업체명 |
rows[].place_id | string | 네이버 플레이스 ID |
rows[].is_mine | bool | 내 플레이스 여부 |
rows[].visitor_cnt | int|null | 방문자 리뷰 수 |
rows[].blog_cnt | int|null | 블로그·카페 리뷰 수 |
rows[].review_score | string|null | 평점(5점 만점). 저장값 그대로라 소수 2자리 문자열("4.87") |
rows[].d7 | string|null | 정보충실성 점수(0~100, 소수 3자리 문자열). 상세 수집한 업체만 값이 있습니다 |
rows[].n1 | string|null | 키워드 일치 점수(자체 추정치, 소수 3자리 문자열) |
rows[].n2 | string|null | 경쟁력 종합 점수(자체 추정치, 소수 3자리 문자열) |
rows[].n3 | string|null | 순위 환산 점수(자체 추정치, 소수 3자리 문자열) |
rows[].tier | int|null | 1 = 목록 지표만 수집 / 2 = 상세까지 수집(상위 max(detail_top, 10) 이내 또는 내 플레이스) |
explain | object|null | 내 플레이스 점수 해설. 내 플레이스의 상세 수집분이 없으면 null |
explain.components | object | N1 구성요소 — 키워드를 지역어/업종어로 분해해 매칭한 결과 |
explain.components.L | float|null | 지역 일치 0~1 (주소 1.0 / 상호 0.7 / 대표키워드 0.5 / 불일치 0) |
explain.components.B | float|null | 업종 일치 0~1 (업종 경로 또는 카테고리명 일치 1.0 / 동일 계열 0.8) |
explain.components.T | float|null | 대표키워드 일치 0~1 (전체 키워드 1.0 / 업종어 0.6 / 지역 핵심어 0.4) |
explain.components.M | float|null | 상호 일치 0~1 (지역 핵심어 포함 1.0 / 업종어 포함 0.6) |
explain.components.region | string | 키워드에서 분리한 지역어 |
explain.components.core | string | 지역 핵심어(역·동·구 등 접미 제거) |
explain.components.bizterm | string | 키워드에서 분리한 업종어 |
explain.seo[] | array | D7 정보충실성 체크리스트 항목 |
explain.seo[].label | string | 항목명 — 메뉴/시술·대표키워드·찾아오는길·대표사진·영업시간 공개·예약 연결·가격 공개·필수정보 완성·톡톡/챗봇·스타일리스트·편의시설·부가 카테고리·결제수단 |
explain.seo[].raw | string | 현재 상태 표시값("24개", "공개", "누락 없음" 등) |
explain.seo[].grade | float | 항목 달성도 0~1 |
explain.seo[].w | float | 항목 가중치(0.5~1.5) |
explain.seo[].avail | int | 1 = 이 업종에 해당(D7 에 반영) / 0 = 미해당(계산에서 제외) |
explain.dims | object|null | 내 플레이스의 저장된 점수 원본 — d1~d10·n1·n2·n3. DB 저장값 그대로라 각 값은 소수 3자리 문자열(미산출 항목은 null) |
explain.review_kw | object|null | 방문자 리뷰 AI 분석 키워드 — menus·themes·voted 각각 [{"l": 라벨, "c": 건수}] |
explain.review_weekly | object|null | 주별 리뷰 누적 — v(방문자)·b(블로그) 각 4칸 배열, 순서대로 최근 1·2·3·4주 누적 건수 |
explain.review_quality | object|null | 최근 4주 방문자 리뷰 품질 지표 |
explain.review_quality.photo_n | int | 사진 첨부 리뷰 수 |
explain.review_quality.photo_total | int | 집계 대상 리뷰 수(최근 4주) |
explain.review_quality.photo_ratio | float | 사진 첨부 비율 0~1 |
explain.review_quality.ctx | object | 방문 맥락별 건수({"예약 후 이용": 14} 형태, 건수 내림차순) |
explain.review_quality.authority | object | 리뷰어 영향력 — infl(팔로워 100+ 리뷰어 수) · hi_infl(그중 평점 4.5+ 부여) · power(리뷰 100건+ 리뷰어 수) · avg_fol(평균 팔로워) · top[](상위 5명: n 닉네임, f 팔로워, r 리뷰수, rt 부여 평점) |
explain.review_quality.bloggers[] | array | 블로그 리뷰 작성자(최대 8명) — id(블로그 ID) · n(필명). 수집된 경우에만 포함 |
series[] | array | 내 플레이스의 일자별 추이(오래된 순) — ymd·n1·n2·n3·rnk. n1~n3 는 저장값 그대로라 소수 3자리 문자열, rnk 는 정수. 분석을 실행한 날짜만 존재합니다 |
아래 지표는 모두 0~100 으로 정규화한 랭크프리 자체 추정치이며 네이버 공식 점수가 아닙니다. 값은 그날 수집한 상위 30개 경쟁셋을 모집단으로 계산되므로, 다른 키워드끼리는 직접 비교하지 마세요.
| 지표 | N2 가중 | 산출 기준 |
|---|---|---|
d1 | 0.18 | 방문자 리뷰 수 — 경쟁셋 90분위 기준 로그 정규화 |
d2 | 0.09 | 블로그·카페 리뷰 수 |
d3 | 0.07 | 예약자 리뷰 수. 미제공 업종은 리뷰 방문맥락 "예약 후 이용" 건수로 대체 |
d4 | 0.12 | 평점 — 방문자 수로 보정한 베이지안 평균 |
d5 | 0.08 | 저장 수 — 음식점(restaurant)만 산출, 그 외 업종은 null |
d6 | 0.08 | 등록 사진 수 |
d7 | 0.14 | 정보충실성 — explain.seo 체크리스트 가중평균 |
d8 | - | 키워드 일치 — L .30 / B .30 / T .30 / M .10. n1 과 동일 값 |
d9 | 0.20 | 최근 리뷰 유입 — 최근 4주 누적 방문자+블로그 리뷰 수 |
d10 | 0.12 | 리뷰어 영향력 — 팔로워·리뷰수 기반 권위 점수의 경쟁셋 내 백분위 |
n1 | - | d8 과 동일 — 키워드와 업체 정보의 일치도 |
n2 | - | d1·d2·d3·d4·d5·d6·d7·d9·d10 가중평균. 수집되지 않은 지표(null)는 가중치를 재정규화해 제외(표의 가중치는 기본값) |
n3 | - | 순위 환산 — 100 x (1 - ln(min(rnk,300)) / ln 301). 미노출(rnk = 300)이면 약 0.058 |
/compete/{slotId}/analyze
분석 실행(동기, 수십 초 소요)
지금 네이버에서 해당 키워드의 상위 30개 업체를 수집하고, 상위 max(detail_top, 10) 곳(내 플레이스는 순위와 무관하게 항상 포함)의 상세 정보를 추가로 읽어 점수를 계산·저장합니다. 주별 리뷰 수집은 상위 10곳 + 내 플레이스로 고정이며, 리뷰 수집 대상은 상세도 함께 읽히므로 detail_top 을 10 미만으로 주더라도 상위 10곳은 상세까지 수집됩니다. 동기 처리로 수십 초가 걸리므로 클라이언트 타임아웃을 넉넉히(권장 300초) 설정하세요. 실행 결과 요약만 즉시 반환하며, 경쟁셋 표·해설은 이어서 GET /compete/{slotId} 로 조회합니다. 같은 날 다시 실행하면 그날 스냅샷을 덮어씁니다.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
slotId | 필수 | int | 경로 파라미터. 순위추적 슬롯 ID(없는 ID 는 404, 본인 소유가 아니면 403) |
detail_top | 선택 | int | 상세 정보를 수집할 상위 개수(기본 10). 값이 클수록 정확도가 올라가지만 소요 시간이 늘어납니다. 리뷰 주별 수집은 상위 10곳 + 내 플레이스로 고정이며 그 대상은 상세도 함께 수집되므로, 실효 기준은 max(detail_top, 10) 입니다(10 미만을 줘도 상위 10곳은 tier 2) |
curl -X POST https://rankfree.kr/api/v1/compete/12/analyze \
-H "Authorization: Bearer rk_..." \
-H "Content-Type: application/json" \
-d '{"detail_top": 10}' \
--max-time 300{
"blocked": false,
"my_rank": 3,
"total": 284,
"competitors": 30,
"my_score": {
"d1": 88.24,
"d2": 71.503,
"d3": 64.118,
"d4": 82.667,
"d5": null,
"d6": 59.42,
"d7": 78.4,
"d8": 82.5,
"d9": 66.315,
"d10": 73.333,
"n1": 82.5,
"n2": 74.132,
"n3": 68.041,
"act": null,
"mask": 1007,
"tier": 2,
"rnk": 3
}
}HTTP/1.1 429 Too Many Requests
{
"message": "조회 제한(nCaptcha 토큰 재발급 필요)",
"blocked": true
}네이버가 순위 조회를 차단하면 위 응답이 내려오며 이 호출로 저장된 데이터는 없습니다. 잠시 뒤(수 분 이상 간격) 재시도하세요. 같은 429 라도 blocked 키 없이 daily_limit·used 가 오면 API 키의 일일 호출 한도 초과입니다.
| 필드 | 타입 | 설명 |
|---|---|---|
blocked | bool | 정상 응답(200)에서는 항상 false. 차단은 429 + blocked: true |
my_rank | int | 내 플레이스 순위(1~). 상위 300위 안에서 발견되지 않으면 300(저장되는 rnk 도 300). 슬롯에 플레이스가 지정되지 않은 경우에도 300 이며, 0 은 내려가지 않습니다 |
total | int | 해당 키워드의 네이버 검색 결과 전체 업체 수 |
competitors | int | 이번에 수집한 상위 목록 개수(최대 30). 내 플레이스가 상위 30 밖이라 따로 수집·저장된 행은 포함하지 않습니다 |
my_score | object|null | 내 플레이스 점수. 슬롯에 플레이스가 지정되지 않았으면 null. 계산 직후 값이라 각 점수는 문자열이 아닌 숫자입니다 |
my_score.d1 ~ d10 | float|null | 세부 지표(0~100). 수집 불가 항목은 null — 위 "점수 지표 정의" 참고 |
my_score.n1 | float|null | 키워드 일치 점수(자체 추정치) |
my_score.n2 | float|null | 경쟁력 종합 점수(자체 추정치) |
my_score.n3 | float | 순위 환산 점수(자체 추정치). 순위가 미노출이면 rnk 300 으로 계산되어 약 0.058 이 되며, null 은 나오지 않습니다 |
my_score.act | null | 예약 필드 — 현재 항상 null |
my_score.mask | int | 산출에 성공한 지표 비트마스크. bit0부터 순서대로 d1·d2·d3·d4·d5·d7·d8·d9·d10·d6 (예: 1007 = d5 만 결측) |
my_score.tier | int | 1 = 목록 지표만 / 2 = 상세까지 수집 |
my_score.rnk | int | 스냅샷에 저장된 순위. 미노출이면 300 |
/keyword/products수집 상품 목록 (scope: keyword_products)keyword를 생략하면 모든 키워드의 수집 상품을 조회합니다. 이때 수집 시각 내림차순 → 키워드 오름차순 → 순위 오름차순(동률은 매핑 ID)으로 정렬합니다. keyword를 지정하면 기존처럼 순위순으로 조회합니다. 저장된 상품만 반환하며 새 수집을 시작하지 않습니다. 키와 회원 모두 keyword_products(수집 상품 목록) 권한이 필요합니다. 콘솔 API 키 만들기·권한 수정에서 선택할 수 있으며, 회원 권한은 어드민 회원 관리에서 설정합니다. 기존 keyword 권한이 있던 키·회원은 배포 시 새 권한도 부여합니다. 이후 두 권한은 독립적입니다. 기존 키의 일일 한도와 분당 60회 제한을 적용하며 검색량 분석 월 한도는 차감하지 않습니다.
| 이름 | 뜻 |
|---|---|
keyword | 선택 문자열, 최대 120자. 생략·빈 문자열·공백은 전체 키워드 조회입니다. 지정하면 양끝 공백만 제거합니다. |
month | 선택, YYYYMM(예: 202610). 생략하면 keyword 지정 시 해당 키워드의 최신 수집 월, keyword 생략 시 전체 키워드 중 가장 최근 수집 월입니다(모든 과거 월을 합치지 않음). 어드민과 동일한 월별 저장분이며 날짜별 이력·기간 조회는 제공하지 않습니다. |
page / limit | page 기본 1(1~1000000), limit 기본 20(1~100). |
curl --get https://rankfree.kr/api/v1/keyword/products \ -H "Authorization: Bearer <API_KEY>" \ --data-urlencode "keyword=경량패딩" --data "month=202610" --data "limit=10"
curl --get https://rankfree.kr/api/v1/keyword/products -H "Authorization: Bearer <API_KEY>" --data "page=1" --data "limit=20"
{"data":{"keyword":"경량패딩","status":"collected","month":202610,"available_months":[202610],"collected_at":"2026-10-26 17:00:00","items":[{"keyword":"경량패딩","rnk":1,"collected_at":"2026-10-26 17:00:00","is_ad":0,"title":"경량패딩 예시","price":39000,"mall_name":"예시몰","store_id":null,"channel_uid":null,"channel_id":null,"channel_no":null,"talk_id":null,"link":"https://smartstore.naver.com/example/products/123","product_key":"pd123"}],"pagination":{"page":1,"limit":10,"total":1,"last_page":1,"has_more":false}}}
전체 조회 시 data.keyword는 null이고, 각 items.keyword는 수집 출처 키워드입니다. 같은 상품이 여러 키워드에서 수집되면 각각 반환하며 pagination.total은 실제 조인된 키워드·상품 매핑 수입니다. items: keyword(수집 키워드), rnk(원본 순위), title(상품명), price(원), mall_name(판매처), link(상품 링크), talk_id(톡톡 코드), is_ad(광고 여부), product_key(식별자), store_id·channel_uid·channel_id·channel_no(스토어 식별자), collected_at(해당 순위 수집 시각). 값이 없으면 null입니다. 이미지·리뷰는 현재 어드민 쇼핑 목록에 저장·표시되지 않아 반환하지 않습니다. 상품명·가격 등은 어드민과 같은 최신 상품 마스터 값이며 과거 월 당시 가격을 보장하지 않습니다. 시각은 앱 시간대 Asia/Seoul의 YYYY-MM-DD HH:mm:ss입니다.
미수집 키워드·월: 200, status=not_collected, items=[], collected_at=null, pagination.total=0. 존재하는 월의 페이지 범위 초과: status=collected와 빈 items. 키 없음·무효 401, 키 또는 회원 권한 없음 403, 파라미터 오류 422, 호출 한도 초과 429. 아래 검색량 분석의 정규화·월 한도·캐시 규칙은 이 목록 조회에 적용되지 않습니다.
키워드분석 scope: keyword · keyword_detail
네이버 검색광고 데이터로 키워드의 월간 검색량(PC/모바일)·경쟁강도·연관 키워드를 조회합니다.
경량(/keyword)과 상세(/keyword/detail)는
서로 다른 권한(scope)으로 분리되어 있어, 상품별로 키를 발급하고 한도를 따로 관리할 수 있습니다.
상세는 경량 지표에 성별·연령 분포, 최근 12개월 트렌드, 요일별 비율, 자동 인사이트, 공유 토큰이 더해집니다.
| 공통 규칙 | 내용 |
|---|---|
| 인증 | Authorization: Bearer rk_... (또는 X-API-KEY 헤더) |
| 권한(scope) | GET /keyword = keyword, GET /keyword/detail = keyword_detail. 키에 해당 scope 가 없으면 403 |
| 월 한도 | 두 엔드포인트 모두 회원 기능 한도 keyword_analysis 를 호출당 1회 차감. 초과 시 429 + limit_exceeded: true |
| 일일 한도 | 키에 일일 한도가 설정된 경우 초과 시 429. 응답 헤더 X-RateLimit-Limit / X-RateLimit-Remaining 제공 |
| 키워드 정규화 | 서버는 공백 제거 + 영문 대문자로 정규화해 조회합니다. 다만 응답 data.keyword 는 서버 정규화 값이 아니라 검색광고(keywordstool)가 돌려준 키워드 원문으로, 대개 정규화된 형태(강남 미용실 → 강남미용실)입니다. /keyword/detail 에서 검색량 조회만 실패하고 상세만 성공한 경우에는 요청 원문(양끝 공백만 제거)이 그대로 들어가 공백이 남습니다(예: 강남 미용실) |
| 캐시 | 검색량 6시간, 상세(성별·연령·트렌드) 6시간 — 성공 응답만 캐시합니다. 요일별 비율(data.weekday)은 별도 소스(데이터랩)라 규칙이 달라 성공 24시간 · 실패(null) 30분으로 실패도 캐시합니다 |
| 데이터 없음 | 200 + data: null + message. 오류가 아니라 해당 키워드의 집계가 없는 경우입니다 |
| 검색량 표기 | 네이버가 < 10 으로 절사한 값은 정수 5 로 변환해 반환합니다 |
/keyword
경량 — 검색량·경쟁강도·연관 키워드 (scope: keyword)
키워드 1개의 월간 검색량(PC/모바일 분리), 광고 경쟁강도, 연관 키워드 목록을 조회합니다. 연관 키워드는 월간 총 검색량 내림차순으로 정렬되며 개수 제한 없이 전부 반환됩니다.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
keyword | 필수 | string | 분석할 검색 키워드(쿼리스트링). 공백 포함 가능 — 서버가 공백 제거·대문자로 정규화합니다. 빈 값이면 422 |
curl --get https://rankfree.kr/api/v1/keyword \ -H "Authorization: Bearer rk_..." \ --data-urlencode "keyword=강남 미용실"
{
"data": {
"keyword": "강남미용실",
"monthly_pc": 1200,
"monthly_mobile": 8800,
"comp_idx": "높음",
"monthly_total": 10000,
"related": [
{
"keyword": "강남역미용실",
"monthly_pc": 500,
"monthly_mobile": 3800,
"comp_idx": "중간",
"monthly_total": 4300
},
{
"keyword": "신사동미용실",
"monthly_pc": 210,
"monthly_mobile": 1640,
"comp_idx": "낮음",
"monthly_total": 1850
}
]
},
"message": null
}| 필드 | 타입 | 설명 |
|---|---|---|
data | object|null | 조회 결과. 자격증명 문제·집계 없음이면 null(HTTP 200, message 확인) |
data.keyword | string | 검색광고(keywordstool) 응답의 relKeyword 원문 — 서버 정규화 결과가 아니라 네이버가 돌려준 값이며, 통상 정규화된 형태(공백 제거·영문 대문자)입니다. 정규화 키와 정확히 일치하는 행이 없으면 응답의 첫 연관 행을 대표값으로 사용하므로, 요청한 키워드와 다른 키워드가 반환될 수 있습니다 |
data.monthly_pc | int | 최근 30일 PC 검색수. 절사값(< 10)은 5 |
data.monthly_mobile | int | 최근 30일 모바일 검색수 |
data.comp_idx | string|null | 광고 경쟁강도 — 높음 / 중간 / 낮음 |
data.monthly_total | int | monthly_pc + monthly_mobile |
data.related | array | 연관 키워드 목록. monthly_total 내림차순, 개수 제한 없음(수십~수백 건) |
data.related[].keyword | string | 연관 키워드(정규화 형태) |
data.related[].monthly_pc | int | 연관 키워드의 PC 검색수 |
data.related[].monthly_mobile | int | 연관 키워드의 모바일 검색수 |
data.related[].comp_idx | string|null | 연관 키워드의 경쟁강도 |
data.related[].monthly_total | int | 연관 키워드의 월간 총 검색수 |
message | string|null | data 가 null 일 때 사유 문구. 정상 조회 시 null |
회원 기능 한도(keyword_analysis)를 모두 사용한 경우입니다. 두 엔드포인트 공통이며, 다음 달 1일에 초기화됩니다.
HTTP/1.1 429 Too Many Requests
{
"data": null,
"limit_exceeded": true,
"message": "이번 달 키워드 분석 호출 한도(300회)를 초과했습니다."
}/keyword/detail
상세 — 경량 + 성별·연령·트렌드 (scope: keyword_detail)
경량 지표에 더해 성별·연령 분포(성별×연령 버킷 포함), 최근 12개월 검색량 트렌드, 요일별 검색 비율, 자동 인사이트, 검색량 등급(S~F)을 반환합니다. 조회에 성공하면 공개 공유용 share_token 도 함께 발급됩니다. 검색량(경량)과 상세는 소스가 서로 달라 한쪽만 실패할 수 있으며, 검색량만 실패하면 검색량 계열 필드가 응답에서 빠집니다(아래 data 설명 참고). 상세 소스는 별도 세션 기반이라 일시 장애 시 503 이 반환될 수 있습니다.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
keyword | 필수 | string | 분석할 검색 키워드(쿼리스트링). 공백 포함 가능 — 서버가 공백 제거·대문자로 정규화합니다. 빈 값이면 422 |
curl --get https://rankfree.kr/api/v1/keyword/detail \ -H "Authorization: Bearer rk_..." \ --data-urlencode "keyword=강남 미용실"
{
"data": {
"keyword": "강남미용실",
"monthly_pc": 1200,
"monthly_mobile": 8800,
"comp_idx": "높음",
"monthly_total": 10000,
"related": [
{
"keyword": "강남역미용실",
"monthly_pc": 500,
"monthly_mobile": 3800,
"comp_idx": "중간",
"monthly_total": 4300
}
],
"grade": "B",
"weekday": [
{ "w": "월", "pct": 15.8 },
{ "w": "화", "pct": 14.9 },
{ "w": "수", "pct": 14.1 },
{ "w": "목", "pct": 14.4 },
{ "w": "금", "pct": 15.2 },
{ "w": "토", "pct": 13.0 },
{ "w": "일", "pct": 12.6 }
],
"detail": {
"gender": {
"female": 7200,
"male": 2800,
"female_pct": 72.0,
"male_pct": 28.0
},
"age": [
{ "age": "25-29", "total": 2400, "pct": 24.0 },
{ "age": "30-39", "total": 3900, "pct": 39.0 }
],
"monthly": [
{ "label": "2025-07", "pc": 1100, "mobile": 8300, "total": 9400 },
{ "label": "2025-08", "pc": 1250, "mobile": 9150, "total": 10400 }
],
"buckets": [
{ "gender": "f", "age": "25-29", "pc": 260, "mobile": 1980, "total": 2240 },
{ "gender": "m", "age": "30-39", "pc": 190, "mobile": 1010, "total": 1200 }
],
"insights": {
"cards": [
{ "group": "season", "label": "시즌성", "value": "보통", "color": "var(--color-warning)" },
{ "group": "target", "label": "주 타겟 연령", "value": "30대·20대 후", "color": "var(--color-accent)" }
],
"summary": "이 키워드는 여성(72%) 비중이 높고 30대·20대 후가 검색의 63%를 차지합니다. 어느 정도 시즌을 타는 키워드로, 검색은 4월·5월에 몰리고 1월·2월에 가장 적습니다."
}
}
},
"share_token": "8Kq2mZr7bVdT1sYxLpA0eWnC5gJhF3uQ",
"message": null
}검색광고 HMAC 자격증명 오류·쿼터 소진 등으로 검색량 조회만 실패하고 상세(웹 세션) 조회는 성공한 경우입니다. HTTP 200 이지만 검색량 계열 키가 응답에 존재하지 않으므로 클라이언트는 키 존재 여부를 확인해야 합니다.
{
"data": {
"keyword": "강남 미용실",
"grade": null,
"weekday": null,
"detail": {
"gender": {
"female": 7200,
"male": 2800,
"female_pct": 72.0,
"male_pct": 28.0
},
"age": [
{ "age": "30-39", "total": 3900, "pct": 39.0 }
],
"monthly": [
{ "label": "2025-08", "pc": 1250, "mobile": 9150, "total": 10400 }
],
"buckets": [
{ "gender": "f", "age": "30-39", "pc": 300, "mobile": 2100, "total": 2400 }
],
"insights": null
}
},
"share_token": "8Kq2mZr7bVdT1sYxLpA0eWnC5gJhF3uQ",
"message": null
}| 필드 | 타입 | 설명 |
|---|---|---|
data | object|null | 검색량 조회에 성공하면 경량 응답의 모든 필드(keyword·monthly_pc·monthly_mobile·comp_idx·monthly_total·related)를 그대로 포함합니다. 검색량만 실패하고 상세만 성공하면(검색광고 자격증명 오류·쿼터 소진 등, 웹 세션은 정상) keyword·grade·weekday·detail 만 담기고 monthly_pc·monthly_mobile·comp_idx·monthly_total·related 키는 아예 존재하지 않습니다(이때 keyword 는 정규화되지 않은 요청 원문). 검색량·상세가 모두 실패하면 null(HTTP 200, message 확인) |
data.grade | string|null | 검색량 등급(자체 추정) — S(10만↑)·A(3만↑)·B(1만↑)·C(3천↑)·D(1천↑)·E(100↑)·F. 검색량 조회 실패 시 null |
data.weekday | array|null | 최근 90일 요일별 검색 비율(월~일 7개). 검색량 조회가 실패하면 데이터랩과 무관하게 항상 null(요일 조회 자체를 시도하지 않음). 검색량이 성공해도 데이터랩 조회가 불가하면 null |
data.weekday[].w | string | 요일 — 월·화·수·목·금·토·일 |
data.weekday[].pct | float | 해당 요일 비중(%). 7개 합이 100 |
data.detail | object|null | 성별·연령·트렌드 묶음. 해당 키워드의 상세 집계가 없으면 null(HTTP 200 + message) |
data.detail.gender.female | int | 여성 검색수 합계(PC+모바일) |
data.detail.gender.male | int | 남성 검색수 합계(PC+모바일) |
data.detail.gender.female_pct | float | 여성 비중(%), 소수 1자리 |
data.detail.gender.male_pct | float | 남성 비중(%), 소수 1자리 |
data.detail.age | array | 연령대별 집계(최대 7개 밴드) |
data.detail.age[].age | string | 연령 밴드 — 0-12·13-19·20-24·25-29·30-39·40-49·50- |
data.detail.age[].total | int | 해당 연령대 검색수(PC+모바일) |
data.detail.age[].pct | float | 해당 연령대 비중(%), 소수 1자리 |
data.detail.monthly | array | 최근 12개월 검색량 트렌드(과거 → 최근 순) |
data.detail.monthly[].label | string | 월 라벨 — YYYY-MM 형식 |
data.detail.monthly[].pc | int | 해당 월 PC 검색수 |
data.detail.monthly[].mobile | int | 해당 월 모바일 검색수 |
data.detail.monthly[].total | int | 해당 월 총 검색수(pc+mobile) |
data.detail.buckets | array | 성별×연령 교차 버킷(최대 14개) — 원본 분포를 그대로 쓰고 싶을 때 사용 |
data.detail.buckets[].gender | string | f(여성) / m(남성) |
data.detail.buckets[].age | string | 연령 밴드(age[].age 와 동일 코드) |
data.detail.buckets[].pc | int | 해당 버킷 PC 검색수 |
data.detail.buckets[].mobile | int | 해당 버킷 모바일 검색수 |
data.detail.buckets[].total | int | 해당 버킷 총 검색수(pc+mobile) |
data.detail.insights | object|null | 데이터 기반 자동 요약(시즌성·주 타겟). 성별·연령·월별이 모두 비어 있으면 null |
data.detail.insights.cards | array | 지표 카드 목록 |
data.detail.insights.cards[].group | string | 묶음 — season(시즌성·성수기·비수기) / target(성별·연령·핵심 타겟) |
data.detail.insights.cards[].label | string | 카드 제목(예: 시즌성, 성수기, 주 타겟 연령) |
data.detail.insights.cards[].value | string | 표시 문구(예: 뚜렷함, 4월·5월, 여성 72%) |
data.detail.insights.cards[].color | string | 강조 색 CSS 변수 문자열(예: var(--color-accent)). 자체 UI 사용 시 무시해도 됩니다 |
data.detail.insights.summary | string | 한 문단 자연어 요약(성별·연령·시즌성) |
share_token | string|null | 공개 공유 토큰. https://rankfree.kr/keyword/{share_token} 로 로그인 없이 리포트를 열 수 있습니다 |
message | string|null | 상세 데이터가 없을 때 사유 문구. 정상 조회 시 null |
상세 지표 소스(검색광고 세션)에 연결할 수 없을 때 반환합니다. 데이터 없음(200 + detail: null)과 구분되는 일시 장애이므로, 잠시 후 같은 요청을 재시도하면 됩니다.
HTTP/1.1 503 Service Unavailable
{
"data": null,
"message": "상세 분석 소스에 일시적으로 연결할 수 없습니다. 잠시 후 다시 시도하세요."
}마케팅 상품 주문 scope: order
플레이스·쇼핑 마케팅 상품을 API 로 주문합니다. 순서는 ① 상품 고르기 → ② 상품에 필요한 칸 확인 → ③ 주문 넣기 → ④ 상태 확인 입니다.
주문은 pending(접수)으로 만들어지고, 승인되면 진행됩니다.
/products① 주문할 수 있는 상품 목록판매 중인 상품을 돌려줍니다. 여기서 받은 id 를 주문의 product_id 로 씁니다.
없음(인증 헤더만).
curl https://rankfree.kr/api/v1/products \ -H "Authorization: Bearer rk_..."
{
"products": [
{
"id": 2,
"title": "네이버 플레이스 저장",
"min_quantity": 10,
"max_quantity": 10000,
"min_days": 1,
"fixed_quantity": null,
"fixed_days": null,
"earliest_start_date": "2026-11-07",
"orderable": true
}
]
}| 이름 | 형식 | 뜻 |
|---|---|---|
id | 숫자 | 상품 번호 — 주문 때 product_id 로 보냄 |
title | 글자 | 상품 이름 |
min_quantity · max_quantity | 숫자 | 하루 수량(daily_qty)으로 넣을 수 있는 최소 · 최대 |
min_days | 숫자 | 최소 진행 일수 |
fixed_quantity · fixed_days | 숫자/null | 값이 있으면 하루 수량 · 일수가 그 값으로 정해진 상품(보낸 값 대신 이 값으로 접수) |
earliest_start_date | 날짜 | 넣을 수 있는 가장 빠른 시작일 |
orderable | 참/거짓 | false 면 API 로 주문할 수 없는 상품(파일 첨부가 필요한 상품) |
/products/{id}② 상품에 필요한 칸 확인상품 하나의 정보와 주문 때 채워야 하는 칸 목록(fields)을 돌려줍니다. required: true 인 칸은 모두 주문의 fields 에 넣어야 합니다.
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
id (주소 안) | 필수 | 숫자 | 상품 번호(목록의 id) |
curl https://rankfree.kr/api/v1/products/2 \ -H "Authorization: Bearer rk_..."
{
"product": {
"id": 2,
"title": "네이버 플레이스 저장",
"earliest_start_date": "2026-11-07",
"orderable": true,
"fields": [
{ "key": "keyword", "label": "검색 키워드", "type": "TEXT", "required": true },
{ "key": "place_url", "label": "플레이스 주소", "type": "URL", "required": true },
{ "key": "daily_qty", "label": "일수량", "type": "NUMBER", "required": true },
{ "key": "start_date", "label": "시작일", "type": "DATE", "required": true },
{ "key": "end_date", "label": "종료일", "type": "DATE", "required": true }
]
}
}없거나 판매 중이 아닌 상품은 404 입니다.
/orders③ 주문 넣기 · 분당 30회JSON 으로 보냅니다. 상품 번호는 맨 바깥에, 나머지 입력값은 모두 fields 안에 넣습니다. 성공하면 201 과 주문 정보를 돌려줍니다.
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
product_id | 필수 | 숫자 | 1 |
fields.keyword | 필수 | 글자 | 노출을 원하는 검색 키워드(예: 연남동 맛집) |
fields.place_url | 필수 | URL | 네이버 플레이스 주소(예: https://m.place.naver.com/restaurant/1234567890) |
fields.field_6 | 필수 | 글자 | 유입 키워드 |
fields.short_url | 선택 | URL | 정답으로 쓸 플레이스 단축 주소(예: https://naver.me/abcd1234). 넣으면 이 주소를 정답으로 진행. 안 넣으면 비워 두세요 |
fields.start_date | 필수 | YYYY-MM-DD | 시작일. earliest_start_date 보다 빠르면 안 됨 |
fields.end_date | 필수 | YYYY-MM-DD | 종료일. 보내도 시작일부터 5일로 접수됨 |
fields.daily_qty | 필수 | 숫자 | 하루 수량. 보내도 200 으로 접수됨 |
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
product_id | 필수 | 숫자 | 2 |
fields.keyword | 필수 | 글자 | 노출을 원하는 검색 키워드(예: 연남동 맛집) |
fields.place_url | 필수 | URL | 네이버 플레이스 주소(예: https://m.place.naver.com/restaurant/1234567890) |
fields.start_date | 필수 | YYYY-MM-DD | 시작일. earliest_start_date 보다 빠르면 안 됨 |
fields.end_date | 필수 | YYYY-MM-DD | 종료일. 시작일과 같거나 뒤. 시작일~종료일 날짜 수가 진행 일수 |
fields.daily_qty | 필수 | 숫자 | 하루 수량 10~10,000 |
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
product_id | 필수 | 숫자 | 3 |
fields.keyword | 필수 | 글자 | 노출을 원하는 검색 키워드(예: 연남동 맛집) |
fields.shop_url | 필수 | URL | 스마트스토어 상품 주소(예: https://smartstore.naver.com/mystore/products/1234567890) |
fields.start_date | 필수 | YYYY-MM-DD | 시작일. earliest_start_date 보다 빠르면 안 됨 |
fields.end_date | 필수 | YYYY-MM-DD | 종료일. 보내도 시작일부터 3일로 접수됨 |
fields.daily_qty | 필수 | 숫자 | 하루 수량. 보내도 200 으로 접수됨 |
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
product_id | 필수 | 숫자 | 11 |
fields.keyword | 필수 | 글자 | 노출을 원하는 검색 키워드(예: 연남동 맛집) |
fields.shop_url | 필수 | URL | 스마트스토어 상품 주소(예: https://smartstore.naver.com/mystore/products/1234567890) |
fields.start_date | 필수 | YYYY-MM-DD | 시작일. earliest_start_date 보다 빠르면 안 됨 |
fields.end_date | 필수 | YYYY-MM-DD | 종료일. 보내도 시작일부터 3일로 접수됨 |
fields.daily_qty | 필수 | 숫자 | 하루 수량. 보내도 200 으로 접수됨 |
curl -X POST https://rankfree.kr/api/v1/orders \
-H "Authorization: Bearer rk_..." \
-H "Content-Type: application/json" \
-d '{
"product_id": 2,
"fields": {
"keyword": "연남동 맛집",
"place_url": "https://m.place.naver.com/restaurant/1234567890",
"start_date": "2026-11-10",
"end_date": "2026-11-16",
"daily_qty": 100
}
}'curl -X POST https://rankfree.kr/api/v1/orders -H "Authorization: Bearer rk_..." -H "Content-Type: application/json" -d '{
"product_id": 1,
"fields": {
"keyword": "연남동 맛집",
"place_url": "https://m.place.naver.com/restaurant/1234567890",
"field_6": "연남동 파스타",
"short_url": "https://naver.me/abcd1234",
"start_date": "2026-11-12"
}
}'curl -X POST https://rankfree.kr/api/v1/orders \
-H "Authorization: Bearer rk_..." \
-H "Content-Type: application/json" \
-d '{
"product_id": 3,
"fields": {
"keyword": "소금빵",
"shop_url": "https://smartstore.naver.com/mystore/products/1234567890",
"start_date": "2026-11-10",
"end_date": "2026-11-12",
"daily_qty": 200
}
}'수량은 fields 밖 quantity 로 보냅니다.
| 번호 | 상품 | quantity |
|---|---|---|
| 12 | [알고리즘 확산] 전용 팔로워 1,000명 | 1,000 고정(보내도 무시) |
| 13 | [알고리즘 확산] 릴스 - 팔로워 1,000명 전용 · 같은 계정으로 12번을 먼저 주문해야 함 | 릴스 건수 3~30 (3 · 5 · 10 · 20 · 30) |
| 14 | 인스타그램 한국인 팔로워 | 100~30,000 |
| 15 | 인스타그램 계정 육성 1개월 | 1 고정 |
| 16 | 인스타그램 계정 육성 2개월 | 1 고정 |
| 17 | 인스타그램 계정 육성 3개월 | 1 고정 |
| 18 | 인스타그램 계정 육성 라이트 1개월 | 1 고정 |
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
product_id | 필수 | 숫자 | 12 |
fields.insta_url | 필수 | URL | 인스타그램 계정 주소(예: https://www.instagram.com/myshop). instagram.com 이 없으면 422 |
fields.start_date | 필수 | YYYY-MM-DD | 시작일. earliest_start_date 보다 빠르면 안 됨 |
fields.memo | 선택 | 글자 | 어떤 콘텐츠인지 알아볼 메모 |
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
product_id | 필수 | 숫자 | 13 |
quantity | 필수 | 숫자 | 릴스 건수 3~30 (3 · 5 · 10 · 20 · 30) |
fields.insta_url | 필수 | URL | 인스타그램 계정 주소(예: https://www.instagram.com/myshop). instagram.com 이 없으면 422 |
fields.start_date | 필수 | YYYY-MM-DD | 시작일. earliest_start_date 보다 빠르면 안 됨 |
fields.memo | 선택 | 글자 | 어떤 콘텐츠인지 알아볼 메모 |
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
product_id | 필수 | 숫자 | 14 |
quantity | 필수 | 숫자 | 팔로워 명 수 100~30,000 |
fields.insta_url | 필수 | URL | 인스타그램 계정 주소(예: https://www.instagram.com/myshop). instagram.com 이 없으면 422 |
fields.memo | 선택 | 글자 | 어떤 콘텐츠인지 알아볼 메모 |
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
product_id | 필수 | 숫자 | 15 |
fields.insta_url | 필수 | URL | 인스타그램 계정 주소(예: https://www.instagram.com/myshop). instagram.com 이 없으면 422 |
fields.memo | 선택 | 글자 | 어떤 콘텐츠인지 알아볼 메모 |
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
product_id | 필수 | 숫자 | 16 |
fields.insta_url | 필수 | URL | 인스타그램 계정 주소(예: https://www.instagram.com/myshop). instagram.com 이 없으면 422 |
fields.memo | 선택 | 글자 | 어떤 콘텐츠인지 알아볼 메모 |
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
product_id | 필수 | 숫자 | 17 |
fields.insta_url | 필수 | URL | 인스타그램 계정 주소(예: https://www.instagram.com/myshop). instagram.com 이 없으면 422 |
fields.memo | 선택 | 글자 | 어떤 콘텐츠인지 알아볼 메모 |
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
product_id | 필수 | 숫자 | 18 |
fields.insta_url | 필수 | URL | 인스타그램 계정 주소(예: https://www.instagram.com/myshop). instagram.com 이 없으면 422 |
fields.memo | 선택 | 글자 | 어떤 콘텐츠인지 알아볼 메모 |
curl -X POST https://rankfree.kr/api/v1/orders \
-H "Authorization: Bearer rk_..." \
-H "Content-Type: application/json" \
-d '{
"product_id": 13,
"quantity": 10,
"fields": {
"insta_url": "https://www.instagram.com/myshop",
"start_date": "2026-11-10",
"memo": "11월 신메뉴 릴스"
}
}'병원 업종은 정보성 원고만 받습니다.
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
product_id | 필수 | 숫자 | 19 |
fields.keyword | 필수 | 글자 | 블로그 제목·내용에 쓸 키워드. 검색 노출을 원하지 않으면 업체명 |
fields.place_url | 필수 | URL | 네이버 플레이스 주소 |
fields.biz_type | 필수 | 글자 | 일반 또는 병원 |
fields.title_type | 필수 | 글자 | 원고 유형 정보성 또는 후기성. biz_type 이 병원이면 정보성만(후기성은 422) |
fields.image_url | 선택 | URL | 후기성 원고에 쓸 이미지 폴더 주소 |
fields.daily_qty | 필수 | 숫자 | 하루 건수 3~500 |
fields.start_date | 필수 | YYYY-MM-DD | 시작일 |
fields.end_date | 필수 | YYYY-MM-DD | 종료일. 시작일~종료일 5일 이상 |
curl -X POST https://rankfree.kr/api/v1/orders \
-H "Authorization: Bearer rk_..." \
-H "Content-Type: application/json" \
-d '{
"product_id": 19,
"fields": {
"keyword": "강남 치과",
"place_url": "https://m.place.naver.com/hospital/1234567890",
"biz_type": "병원",
"title_type": "정보성",
"image_url": "",
"daily_qty": 3,
"start_date": "2026-11-10",
"end_date": "2026-11-14"
}
}'병원·금융·건강식품·의약품 등 업종은 정보성으로만 작성합니다(biz_type 병원이면 정보성만 접수).
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
product_id | 필수 | 숫자 | 20 |
fields.keyword | 필수 | 글자 | 필수 키워드 3개 이상, / 로 구분 |
fields.blog_tags | 필수 | 글자 | 해시태그(글 하단 세부·메인 키워드) 3개 이상, / 로 구분 |
fields.place_url | 필수 | URL | 플레이스 주소 — 본문에 지도가 들어감 |
fields.shop_intro | 필수 | 글자 | 매장 소개(장점·영업시간·가격·특이사항) |
fields.blog_guide | 필수 | 글자 | 내용 가이드(강조사항) 20자 이내 |
fields.biz_type | 필수 | 글자 | 일반 또는 병원 |
fields.contents_type | 필수 | 글자 | 게시글 유형 정보성 · 후기성 · 자유로운. 병원이면 정보성만 |
fields.daily_qty | 필수 | 숫자 | 하루 건수 5~100 |
fields.start_date | 필수 | YYYY-MM-DD | 시작일 |
fields.end_date | 필수 | YYYY-MM-DD | 종료일. 시작일~종료일 3일 이상 |
curl -X POST https://rankfree.kr/api/v1/orders \
-H "Authorization: Bearer rk_..." \
-H "Content-Type: application/json" \
-d '{
"product_id": 20,
"fields": {
"keyword": "연남동 맛집/연남동 파스타/홍대 데이트",
"blog_tags": "연남동맛집/파스타맛집/데이트코스",
"place_url": "https://m.place.naver.com/restaurant/1234567890",
"shop_intro": "생면 파스타 전문, 매일 11~21시, 2인 세트 3만원대",
"blog_guide": "생면 파스타 맛집 추천해요",
"biz_type": "일반",
"contents_type": "후기성",
"daily_qty": 5,
"start_date": "2026-11-10",
"end_date": "2026-11-12"
}
}'| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
product_id | 필수 | 숫자 | 21 또는 22 |
fields.keyword | 필수 | 글자 | 키워드 |
fields.place_url | 필수 | URL | 플레이스 주소(naver.me 단축 주소 가능) |
fields.blog_guide | 필수 | 글자 | 강조사항 · 리뷰 제외 원하는 휴무일 등 |
fields.review_type | 필수 | 글자 | 후기 또는 포토리뷰 |
fields.image_url | 필수 | URL | 최근 카드 영수증 1장 + (맛집은) 음식 사진을 담은 주소 |
fields.daily_qty | 필수 | 숫자 | 하루 건수 1~5 |
fields.start_date | 필수 | YYYY-MM-DD | 시작일 |
fields.end_date | 필수 | YYYY-MM-DD | 종료일 |
curl -X POST https://rankfree.kr/api/v1/orders \
-H "Authorization: Bearer rk_..." \
-H "Content-Type: application/json" \
-d '{
"product_id": 21,
"fields": {
"keyword": "연남동 맛집",
"place_url": "https://naver.me/abcd1234",
"blog_guide": "월요일 휴무",
"review_type": "포토리뷰",
"image_url": "https://drive.google.com/drive/folders/...",
"daily_qty": 2,
"start_date": "2026-11-10",
"end_date": "2026-11-19"
}
}'{
"order": {
"order_no": "MO2611061A2B3C",
"status": "pending",
"status_label": "접수",
"product": { "id": 2, "title": "네이버 플레이스 저장" },
"quantity": 100,
"days": 7,
"fields": {
"keyword": "연남동 맛집",
"place_url": "https://m.place.naver.com/restaurant/1234567890",
"start_date": "2026-11-10",
"end_date": "2026-11-16",
"daily_qty": "100"
},
"created_at": "2026-11-06T14:30:00+09:00"
}
}| 이름 | 형식 | 뜻 |
|---|---|---|
order.order_no | 글자 | 주문번호 — 상태 확인 때 씀 |
order.status | 글자 | pending 접수 · processing 진행중 · completed 완료 · canceled 취소 |
order.status_label | 글자 | 상태 한글 이름 |
order.product | 객체 | 상품 번호 · 이름 |
order.quantity · order.days | 숫자 | 접수된 하루 수량 · 진행 일수 |
order.fields | 객체 | 접수된 입력값(숫자도 글자로 돌아옴) |
order.created_at | 날짜시각 | 접수 시각(한국 시간) |
오류는 message(이유)와 field(문제 칸)로 옵니다.
| 코드 | field | 이유 · 고치는 법 |
|---|---|---|
| 404 | product_id | 없는 상품이거나 판매 중이 아님 → GET /products 로 번호 확인 |
| 422 | product_id | 파일 첨부가 필요한 상품(orderable: false) → API 로 주문 불가 |
| 422 | f_칸이름 | 칸 빠짐 · 값이 틀림 · 시작일이 earliest_start_date 보다 빠름 (예: f_place_url) |
| 422 | quantity | 하루 수량(인스타 상품은 quantity)이 최소~최대 밖 |
| 422 | f_칸이름 | 정해진 값 밖(예: review_type) · 병원 업종에 정보성 아닌 유형 · 키워드/해시태그 / 구분 3개 미만 · 내용 가이드 20자 초과 · 인스타그램 주소 아님 |
| 422 | days | 시작일·종료일 빠짐 · 종료일이 시작일보다 빠름 · 최소 일수보다 짧음 |
| 422 | balance | API 주문은 결제 금액(부가세 포함)을 충전금에서 바로 뺍니다. 충전금이 모자라면 주문을 받지 않습니다 — 예: 충전금이 부족합니다(필요 330,000원, 잔액 100,000원) |
{
"message": "'플레이스 주소' 항목을 입력하세요.",
"field": "f_place_url"
}/orders④ 내 주문 목록내 주문을 최신순으로 돌려줍니다. 항목 모양은 POST /orders 응답의 order 와 같습니다.
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
status | 선택 | 글자 | 이 상태만: pending · processing · completed · canceled |
page | 선택 | 숫자 | 쪽 번호(기본 1) |
per_page | 선택 | 숫자 | 한 쪽 건수(기본 20, 최대 100) |
curl "https://rankfree.kr/api/v1/orders?status=processing&page=1&per_page=20" \ -H "Authorization: Bearer rk_..."
{
"orders": [ { "order_no": "MO2611061A2B3C", "status": "processing", "product": { "id": 2, "title": "네이버 플레이스 저장" } } ],
"meta": { "page": 1, "per_page": 20, "total": 37, "last_page": 2 }
}/orders/{orderNo}④ 주문 한 건 상태 확인주문번호로 내 주문 한 건을 돌려줍니다. 응답은 POST /orders 응답과 같은 order 입니다.
| 이름 | 필수 | 형식 | 뜻 |
|---|---|---|---|
orderNo (주소 안) | 필수 | 글자 | 주문번호(order_no) |
curl https://rankfree.kr/api/v1/orders/MO2611061A2B3C \ -H "Authorization: Bearer rk_..."
없거나 내 주문이 아니면 404 입니다.
쇼핑 유입키워드 scope: shop_keyword
핵심 키워드 하나와 내 상품 URL 로 롱테일 키워드 조합을 자동 생성하고, 각 조합으로 네이버 쇼핑을 검색해 내 상품이 상위 N위 안에 노출되는 키워드만 골라냅니다. 분석을 만들면 순위 확인이 자동으로 끝까지 진행되며, 찾아낸 노출 키워드는 그룹으로 나눠 Short URL 로 바로 발급할 수 있습니다(규칙은 관리자 화면과 동일).
보내는 값은 핵심 키워드와 상품 URL 두 개뿐입니다. 상품 제목 · 상점명 · 가격 같은 상품 정보는 랭크프리가 알아서 수집합니다 — 따로 넣을 값이 없습니다. 수집에는 요청자 계정으로 로그인된 랭크프리 확장 프로그램이 필요하며, 확장이 켜져 있으면 화면을 열어 둘 필요 없이 자동으로 처리됩니다.
수집된 상품 정보로 롱테일 키워드를 자동으로 만들어, 키워드마다 쇼핑을 검색해
내 상품이 상위 N위 안에 노출되는지 확인합니다. 이 확인도 확장 프로그램이 이어서 처리합니다.
생성 응답은 확인을 기다리지 않고 바로 돌아오므로, 진행률은 GET /shop-keywords/{id} 의 progress 로 폴링합니다.
노출로 판정된 키워드(exposed_keywords)를 원하는 그룹 수로 나눠
그룹별 단축 URL 을 발급합니다. 발급된 url 과 배정 키워드를 발주 · 배포 시스템이 그대로 가져다 씁니다.
| 항목 | 공통 규칙 |
|---|---|
| Base URL | https://rankfree.kr/api/v1 — 아래 모든 경로 앞에 붙습니다. |
| 인증 | Authorization: Bearer rk_... 헤더. API 키에 shop_keyword 스코프가 있어야 합니다. |
| 소유권 | 키 소유자가 만든 분석만 접근할 수 있습니다. 남의 id 를 조회하면 403. |
| 호출 한도 | 생성 · 변경 계열(POST)은 분당 30회. |
status | pending(상품 정보 수집 대기 — 확장이 채우면 자동으로 checking 으로 넘어갑니다) · checking(확인 중) · done(완료) · blocked(차단으로 중단) · paused(사용자 중단) |
| 확장 프로그램 | 상품 정보 수집과 순위 확인은 요청자 계정으로 로그인된 랭크프리 확장이 담당합니다. 확장이 켜져 있지 않으면 진행되지 않습니다(화면을 열어 둘 필요는 없습니다). |
| 중복 요청 | 같은 키워드 + 같은 상품으로 다시 요청하면 새로 만들지 않고 기존 분석을 그대로 돌려줍니다(응답에 reused: true, 상태코드 200). 새로 생성될 때만 201 입니다. |
progress | total(전체 조합) · checked(확인 완료) · remaining(남은 조합) · exposed(노출 판정 수). remaining 이 0 이면 확인 종료입니다. |
/shop-keywords
분석 생성 + 순위확인 자동 시작
핵심 키워드와 상품 URL 두 개로 분석을 만듭니다. 상품 정보 수집 · 키워드 생성 · 순위 확인이 이어서 자동으로 진행되며, 응답은 이를 기다리지 않고 바로 돌아옵니다. 진행 상태는 GET /shop-keywords/{id} 로 폴링하세요.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
core_keyword | 필수 | string | 핵심 키워드(최대 120자). 모든 조합에 이 키워드가 포함됩니다. |
product | 필수 | string | 내 상품 URL(최대 500자). 스마트스토어 · 브랜드스토어 · 가격비교 catalog URL 을 인식해 product_id 를 자동 추출합니다. URL 이 아니면 업체명으로 취급해 상품이 아닌 업체 단위로 매칭합니다. |
threshold | 선택 | int | 노출로 볼 순위 기준 — 4 또는 5만 쓸 수 있습니다(기본 5). 이 순위 이내면 노출로 판정합니다. |
curl -X POST https://rankfree.kr/api/v1/shop-keywords \
-H "Authorization: Bearer rk_..." \
-H "Content-Type: application/json" \
-d '{
"core_keyword": "비타민c",
"product": "https://smartstore.naver.com/healthyday/products/6412870193",
"threshold": 5
}'HTTP/1.1 201 Created
{
"analysis": {
"id": 42,
"core_keyword": "비타민c",
"product_url": "https://smartstore.naver.com/healthyday/products/6412870193",
"product_id": "6412870193",
"mall_name": "",
"product_title": "",
"brand": "",
"product_price": 0,
"threshold": 5,
"status": "pending",
"progress": {
"total": 0,
"checked": 0,
"remaining": 0,
"exposed": 0,
"blocked": false
},
"created_at": "2026-07-29T14:20:11+09:00",
"product": {
"title": "",
"brand": "",
"mall_name": "",
"price": 0,
"category": "",
"thumbnail_url": "",
"seller_tags": [],
"collected_at": null
}
}
}※ 생성 직후에는 아직 상품 정보를 수집하기 전이라 product 가 비어 있고 status 는 pending 입니다. 확장이 수집하면 값이 채워지고 checking 으로 넘어갑니다 — 진행은 상세 조회로 폴링하세요.
| 필드 | 타입 | 설명 |
|---|---|---|
analysis.id | int | 분석 ID. 이후 조회 · Short URL 호출의 {id} 입니다. |
analysis.core_keyword | string | 입력한 핵심 키워드 |
analysis.product_url | string | 상품 정보 — 자동 정리된 상품 URL(추적 파라미터 제거). URL 이 아니면 빈 문자열 |
analysis.product_id | string | 상품 정보 — URL 에서 자동 추출한 상품 ID(스마트스토어 channelProductId 또는 가격비교 nvMid). 업체 매칭이면 빈 문자열 |
analysis.mall_name | string | 상품 정보 — 자동 수집된 상점명(입력이 URL 이 아니면 입력한 업체명). 수집 전에는 빈 문자열 |
analysis.product_title | string | 자동 수집된 상품 제목. 수집 전에는 빈 문자열 |
analysis.brand | string | 자동 수집된 브랜드 · 제조사 |
analysis.product_price | int | 자동 수집된 판매가(원). 수집 전에는 0 |
analysis.threshold | int | 노출 판정 기준 순위 |
analysis.status | string | 진행 상태. 조합이 0개면 즉시 done |
analysis.progress.total | int | 생성된 조합 수 |
analysis.progress.checked | int | 순위 확인이 끝난 조합 수 |
analysis.progress.remaining | int | 남은 조합 수(0 이면 확인 종료) |
analysis.progress.exposed | int | 1~threshold 위로 확인된 조합 수 |
analysis.progress.blocked | bool | 차단으로 확인이 멈췄는지 여부 |
analysis.created_at | string | 생성 시각(ISO 8601, KST) |
analysis.product | object | 수집된 상품 정보 — 모든 응답(생성 · 목록 · 상세)에 같은 모양으로 들어갑니다. 생성 직후에는 아직 수집 전이라 빈 값이고, 확장이 채우면 값이 들어옵니다 |
analysis.product.seller_tags | array | 해시태그(관련 태그) — 상품 상세페이지 하단 태그. # 없이 문자열 배열 |
analysis.product.thumbnail_url | string | 대표이미지 URL |
analysis.product.title · brand · mall_name · price · category | — | 상품 제목 · 브랜드 · 상점명 · 판매가(원) · 카테고리 |
analysis.product.collected_at | string | 상품 정보 수집 시각(ISO 8601). 수집 전이면 null |
/shop-keywords
내 분석 목록
내 API 키로 만든 분석을 최신순으로 조회합니다. 각 항목은 생성 응답과 동일한 analysis 페이로드(진행률 포함)입니다.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
page | 선택 | int | 페이지 번호(기본 1) |
per_page | 선택 | int | 페이지당 개수(1~100, 기본 20) |
curl "https://rankfree.kr/api/v1/shop-keywords?page=1&per_page=20" \ -H "Authorization: Bearer rk_..."
{
"analyses": [
{
"id": 42,
"core_keyword": "비타민c",
"product_url": "https://smartstore.naver.com/healthyday/products/6412870193",
"product_id": "6412870193",
"mall_name": "헬씨데이",
"threshold": 5,
"status": "done",
"progress": {
"total": 240,
"checked": 240,
"remaining": 0,
"exposed": 12,
"blocked": false
},
"created_at": "2026-07-29T14:20:11+09:00"
},
{
"id": 41,
"core_keyword": "루테인",
"product_url": "https://smartstore.naver.com/healthyday/products/6390112044",
"product_id": "6390112044",
"mall_name": "헬씨데이",
"threshold": 5,
"status": "checking",
"progress": {
"total": 180,
"checked": 96,
"remaining": 84,
"exposed": 5,
"blocked": false
},
"created_at": "2026-07-28T09:41:07+09:00"
}
],
"page": 1,
"per_page": 20,
"total": 2
}| 필드 | 타입 | 설명 |
|---|---|---|
analyses[] | array | 분석 목록(최신순). 각 항목의 구조는 생성 응답의 analysis 와 동일 |
page | int | 현재 페이지 |
per_page | int | 페이지당 개수 |
total | int | 전체 분석 수 |
/shop-keywords/{id}
진행 상태 · 상품 정보 · 노출 키워드
분석 하나의 진행률 · 저장된 상품 정보 · 노출 판정 키워드 · 발급된 Short URL 을 한 번에 돌려줍니다. 생성 직후에는 progress.remaining 이 0 이 될 때까지 몇 초 간격으로 폴링하면 됩니다.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
id | 필수 | int | 경로 파라미터. 분석 ID(내 소유가 아니면 403) |
curl https://rankfree.kr/api/v1/shop-keywords/42 \ -H "Authorization: Bearer rk_..."
{
"analysis": {
"id": 42,
"core_keyword": "비타민c",
"product_url": "https://smartstore.naver.com/healthyday/products/6412870193",
"product_id": "6412870193",
"mall_name": "헬씨데이",
"product_title": "헬씨데이 비타민C 1000mg 고함량 스틱 30포",
"brand": "헬씨데이",
"product_price": 19900,
"threshold": 5,
"status": "done",
"progress": {
"total": 240,
"checked": 240,
"remaining": 0,
"exposed": 12,
"blocked": false
},
"created_at": "2026-07-29T14:20:11+09:00",
"product": {
"title": "헬씨데이 비타민C 1000mg 고함량 스틱 30포",
"brand": "헬씨데이",
"mall_name": "헬씨데이",
"price": 19900,
"category": "건강기능식품",
"thumbnail_url": "https://shop-phinf.pstatic.net/.../6412870193.jpg",
"seller_tags": ["고함량비타민", "비타민스틱", "휴대용비타민"],
"collected_at": "2026-07-29T14:20:40+09:00"
}
},
"exposed_keywords": [
"비타민c 고함량 스틱",
"헬씨데이 비타민c 1000mg 30포"
],
"short_links": [
{
"group_no": 1,
"url": "https://rankfree.kr/s/AbC123xYz9K",
"keywords": [
"비타민c 고함량 스틱"
],
"hit_count": 0
}
]
}| 필드 | 타입 | 설명 |
|---|---|---|
analysis.id | int | 분석 ID(요청한 {id} 와 동일) |
analysis.product_url | string | 상품 정보 — 정리된 상품 URL(쿼리스트링 제거) |
analysis.product_id | string | 상품 정보 — 서버가 URL 에서 자동 추출한 상품 ID |
analysis.mall_name | string | 상품 정보 — 자동 수집된 상점명. 수집 전에는 빈 문자열 |
analysis.product_title · analysis.brand · analysis.product_price | string · string · int | 자동 수집된 상품 제목 · 브랜드 · 판매가 |
analysis.product | object | 수집된 상품 정보 전체 — 아래 항목을 담습니다(상세 조회에만 포함) |
analysis.product.seller_tags | array | 해시태그(관련 태그) — 상품 상세페이지 하단의 태그 목록. # 없이 문자열 배열로 반환합니다 |
analysis.product.title | string | 상품 제목 |
analysis.product.brand | string | 브랜드 · 제조사 |
analysis.product.mall_name | string | 상점명 |
analysis.product.price | int | 판매가(원) |
analysis.product.category | string | 카테고리 |
analysis.product.thumbnail_url | string | 대표이미지 URL |
analysis.product.collected_at | string | 상품 정보를 수집한 시각(ISO8601). 아직 수집 전이면 null |
analysis.core_keyword | string | 핵심 키워드 |
analysis.threshold | int | 노출 판정 기준 순위 |
analysis.status | string | checking/done/blocked/paused |
analysis.progress.* | object | total · checked · remaining · exposed · blocked |
analysis.created_at | string | 생성 시각(ISO 8601) |
exposed_keywords | array | 노출 판정(1~threshold 위) 키워드 문자열 배열. 확인 순서를 유지하고 중복은 제거합니다. 3단계 Short URL 의 재료입니다. |
short_links[].group_no | int | 그룹 번호(1부터) |
short_links[].url | string | 발급된 Short URL(아직 생성 전이면 배열이 비어 있음) |
short_links[].keywords | array | 이 그룹에 배정된 키워드 |
short_links[].hit_count | int | 이 링크가 호출된 횟수 |
/shop-keywords/{id}/short-links
Short URL 생성
노출 판정 키워드를 group_count 개 그룹으로 나눠 그룹마다 Short URL 을 새로 발급합니다. 기존 링크는 교체되므로, 이미 배포해 호출이 발생한 링크가 있다면 이 엔드포인트 대신 /reassign 을 사용하세요.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
id | 필수 | int | 경로 파라미터. 분석 ID |
group_count | 필수 | int | 만들 그룹(=Short URL) 수(1~100). 노출 키워드 수보다 클 수 없습니다. |
| 항목 | 규칙 |
|---|---|
| 그룹 분배 | 노출 키워드를 순서대로 그룹에 라운드로빈으로 고르게 나눕니다(1번 → 2번 → … → 1번). |
| 재생성 제한 | 이미 호출된 적이 있는 링크(hit_count > 0)가 하나라도 있으면 생성이 막힙니다 — 배포한 주소가 죽지 않도록 하기 위함입니다. 이때는 /reassign 으로 키워드만 교체하세요. |
| 실패 응답 | 노출 키워드 없음 · 그룹 수 초과 · 호출된 링크 존재는 422 + message + field(group_count) |
curl -X POST https://rankfree.kr/api/v1/shop-keywords/42/short-links \
-H "Authorization: Bearer rk_..." \
-H "Content-Type: application/json" \
-d '{"group_count": 2}'HTTP/1.1 201 Created
{
"short_links": [
{
"group_no": 1,
"url": "https://rankfree.kr/s/AbC123xYz9K",
"keywords": [
"비타민c 고함량 스틱",
"비타민c 스틱 30포"
],
"hit_count": 0
},
{
"group_no": 2,
"url": "https://rankfree.kr/s/Qm7RtV2wLp0",
"keywords": [
"헬씨데이 비타민c 1000mg 30포"
],
"hit_count": 0
}
]
}HTTP/1.1 422
{
"message": "Short URL 개수는 상위 노출 키워드 수보다 많을 수 없습니다.",
"field": "group_count"
}| 필드 | 타입 | 설명 |
|---|---|---|
short_links[].group_no | int | 그룹 번호(1부터 오름차순) |
short_links[].url | string | 발급된 Short URL(보조 도메인이 설정돼 있으면 그룹별로 번갈아 사용) |
short_links[].keywords | array | 이 그룹에 배정된 노출 키워드 |
short_links[].hit_count | int | 호출 횟수(생성 직후 0) |
message | string | 실패(422) 시 사유 |
field | string | 실패(422) 시 원인 필드 — group_count |
/shop-keywords/{id}/short-links
Short URL 목록
발급된 Short URL 만 가볍게 조회합니다. 발주 · 배포 시스템이 링크와 배정 키워드를 그대로 가져다 쓰거나, hit_count 로 호출량을 확인할 때 사용합니다.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
id | 필수 | int | 경로 파라미터. 분석 ID |
curl https://rankfree.kr/api/v1/shop-keywords/42/short-links \ -H "Authorization: Bearer rk_..."
{
"short_links": [
{
"group_no": 1,
"url": "https://rankfree.kr/s/AbC123xYz9K",
"keywords": [
"비타민c 고함량 스틱",
"비타민c 스틱 30포"
],
"hit_count": 128
},
{
"group_no": 2,
"url": "https://rankfree.kr/s/Qm7RtV2wLp0",
"keywords": [
"헬씨데이 비타민c 1000mg 30포"
],
"hit_count": 96
}
]
}| 필드 | 타입 | 설명 |
|---|---|---|
short_links[].group_no | int | 그룹 번호 |
short_links[].url | string | Short URL |
short_links[].keywords | array | 현재 배정된 키워드 |
short_links[].hit_count | int | 누적 호출 횟수 |
/shop-keywords/{id}/short-links/reassign
키워드만 재배정(URL 유지)
이미 배포한 Short URL 주소는 그대로 두고 배정 키워드만 다시 나눕니다. 순위 확인이 더 진행돼 노출 키워드가 늘었을 때 사용합니다(생성은 호출된 링크가 있으면 막히므로 이쪽을 쓰세요). 요청 본문은 필요 없고, 그룹 수는 기존 링크 수를 그대로 유지합니다.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
id | 필수 | int | 경로 파라미터. 분석 ID. 본문 파라미터는 없습니다. |
curl -X POST https://rankfree.kr/api/v1/shop-keywords/42/short-links/reassign \ -H "Authorization: Bearer rk_..."
{
"short_links": [
{
"group_no": 1,
"url": "https://rankfree.kr/s/AbC123xYz9K",
"keywords": [
"비타민c 고함량 스틱",
"비타민c 스틱 30포 휴대용"
],
"hit_count": 128
},
{
"group_no": 2,
"url": "https://rankfree.kr/s/Qm7RtV2wLp0",
"keywords": [
"헬씨데이 비타민c 1000mg 30포",
"비타민c 고함량 1000mg 스틱"
],
"hit_count": 96
}
]
}HTTP/1.1 422
{
"message": "재배정할 Short URL이 없습니다. 먼저 Short URL을 생성하세요.",
"field": "short_links"
}| 필드 | 타입 | 설명 |
|---|---|---|
short_links[].group_no | int | 그룹 번호(1부터 다시 매김) |
short_links[].url | string | 변경되지 않습니다 — 기존 주소 유지 |
short_links[].keywords | array | 새로 배정된 키워드 |
short_links[].hit_count | int | 누적 호출 횟수(초기화되지 않음) |
message | string | 실패(422) 시 사유 — 링크 없음 · 노출 키워드 없음 · 링크 수가 노출 키워드 수보다 많음 |
field | string | 실패(422) 시 short_links |
콘솔에서 직접 발급하고 권한·기간·한도·IP를 설정할 수 있습니다.