R 랭크프리

API 문서

랭크프리 오픈 API — 인증 · 순위추적 · 경쟁분석 · 키워드분석 · 쇼핑 유입키워드 · 마케팅 상품 주문

네이버 플레이스 순위추적, 경쟁분석, 키워드분석, 쇼핑 유입키워드 데이터와 마케팅 상품 주문을 REST API로 제공합니다. API 키는 콘솔 → API 키에서 발급하며, 키마다 권한(scope)·허용기간·일일 호출 한도·허용 IP를 설정할 수 있습니다. 아래 탭에서 주제를 고르면 엔드포인트별 요청 파라미터 · 요청 예시 · 응답 예시 · 응답 필드를 순서대로 볼 수 있습니다.

인증

모든 요청에 Authorization: Bearer 헤더(또는 X-API-KEY)로 키를 전달합니다.

curl -H "Authorization: Bearer rk_xxxxxxxxxxxxxxxx" \
     "https://ops-388a48cadf.rankfree.co.kr/api/v1/rank/slots"
항목
Base URLhttps://ops-388a48cadf.rankfree.co.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 URLhttps://ops-388a48cadf.rankfree.co.kr/api/v1 · 모든 요청에 Authorization: Bearer rk_… (또는 X-API-KEY). rank 권한이 없는 키는 403
rank1 이상 = 노출 순위 · 300 = 상위 300위(6페이지) 밖 · 0 = 조회 불가(차단이거나 키워드·대상 플레이스 미확정). 300위 밖은 항상 300이며 0이 아닙니다 · blocked: true 이면 순위 미확정(rank0, 기록 저장 안 함)
슬롯 한도used는 플레이스 + 쇼핑 순위추적 합산 사용량(활성 슬롯만 집계 — 자동 중단된 슬롯은 빠짐). limit-1 이면 무제한. 한도 초과 등록은 422
history이력을 함께 로드하는 응답(GET /rank/slots, run)에서만 배열이고, 슬롯 등록(POST /rank/slots) 응답에서는 null
소요 시간run·check는 네이버 실시간 조회(최대 6페이지 · 페이지 간 지연)로 수 초~수십 초 걸립니다. 클라이언트 타임아웃을 넉넉히 두세요
소유권내 소유가 아닌 슬롯 idrun·DELETE 호출 시 403
GET /rank/slots 추적 슬롯 목록 + 사용량

내 계정의 플레이스 순위추적 슬롯을 최신 등록순으로 모두 반환합니다(자동 중단된 슬롯 포함). 각 슬롯에는 일자별 순위 이력(history, 날짜 오름차순)이 포함됩니다.

요청 파라미터

없음(인증 헤더만 필요).

요청 예시
curl https://ops-388a48cadf.rankfree.co.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}
      ]
    }
  ]
}
응답 필드
필드타입설명
usedint활성 슬롯 수(플레이스 + 쇼핑 합산). 자동 중단된 슬롯은 세지 않으므로 slots[] 개수보다 작을 수 있습니다
limitint등급별 슬롯 한도(추천 보너스 포함). -1=무제한
slots[]array슬롯 목록(최근 등록순). 자동 중단된 슬롯도 포함되며, 활성 여부를 나타내는 필드는 현재 응답에 없습니다
slots[].idint슬롯 ID — run·DELETE 경로에 사용
slots[].keywordstring추적 키워드
slots[].place_idstring|null네이버 플레이스 ID(숫자). 업체명으로만 등록한 경우 null
slots[].place_namestring|null업체명(등록 시 자동 조회)
slots[].place_urlstring|null정규 모바일 플레이스 URL(m.place.naver.com/place/{id}/home)
slots[].labelstring|null등록 시 지정한 그룹 라벨
slots[].categorystring업종 키(hairshop·restaurant·place 등). 판별하지 못했으면 place가 들어가며 null이 되지 않습니다
slots[].last_rankint|null마지막 조회 순위. 아직 조회 전이면 null
slots[].last_review_countint|null마지막 조회 시점의 방문자 리뷰 수
slots[].last_checked_atstring|null마지막 확인 시각(ISO 8601, KST)
slots[].history[]array|null일자별 순위 기록(오름차순)
slots[].history[].datestring기록 일자(YYYY-MM-DD)
slots[].history[].rankint그날의 순위
POST /rank/slots 슬롯 등록(키워드 다건)

플레이스 1곳에 키워드 N개를 한 번에 등록합니다. 업체명·업종·정규 URL은 서버가 자동 조회해 채웁니다. 이미 추적 중인 키워드는 생성하지 않고 skipped로 돌려줍니다. 등록만 하고 순위 조회는 하지 않으므로 last_ranknull이며, 바로 순위를 얻으려면 이어서 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://ops-388a48cadf.rankfree.co.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_idstring|null확정된 플레이스 ID. 못 찾으면 null
place.place_namestring|null자동 조회한 업체명(ID를 못 찾고 URL도 아니면 입력값 그대로)
place.place_urlstring|null정규 모바일 플레이스 URL
place.categorystring|null업종 키. ID를 못 찾으면 null(이 경우 슬롯에는 place로 저장)
created[]array새로 만들어진 슬롯. 항목 구조는 GET /rank/slotsslots[]와 동일하며 historynull
skipped[]array이미 같은 키워드 × 플레이스로 추적 중이라 건너뛴 키워드 문자열 배열

키워드가 하나도 없거나 슬롯 한도를 넘기면 422와 함께 사유가 message에 담깁니다(예: 추적 한도(100개, 플레이스+쇼핑 합산)를 초과합니다. 현재 100개 사용 중 · 추가 가능 0개(요청 2개).). 한도 검사는 등록 전에 수행되므로 초과 시 슬롯이 하나도 생성되지 않습니다.

GET /rank/resolve 플레이스 메타만 조회

입력한 URL·ID·업체명에서 플레이스 ID를 확정하고 업체명·업종·정규 URL을 돌려줍니다. 슬롯을 만들지 않으므로 등록 화면의 미리보기(입력값이 올바른 플레이스인지 확인)에 사용하세요.

요청 파라미터
파라미터필수타입설명
place필수string플레이스 URL 또는 ID, 업체명(최대 1000자). 쿼리스트링으로 전달
요청 예시
curl -G "https://ops-388a48cadf.rankfree.co.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_idstring|null확정된 플레이스 ID(숫자 문자열). 단축·딥링크 URL은 최종 URL까지 따라가 추출
place.place_namestring|null업체명. ID를 못 찾고 URL도 아니면 입력값을 그대로 업체명으로 반환
place.place_urlstring|null정규 모바일 플레이스 URL. ID를 못 찾았고 입력이 URL이면 입력값 그대로, 아니면 null
place.categorystring|null업종 키(hairshop·restaurant 등). 판별 실패 시 place, ID를 못 찾으면 null
POST /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://ops-388a48cadf.rankfree.co.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}
    ]
  }
}
응답 필드
필드타입설명
resultobject이번 조회 결과 — 구조는 POST /rank/checkresult와 동일
slotobject갱신된 슬롯(이력 포함)
slot.idint슬롯 ID
slot.keywordstring추적 키워드
slot.place_idstring|null플레이스 ID. 비어 있었다면 이번 조회 결과로 채워짐
slot.place_namestring|null업체명. 비어 있었다면 이번 조회 결과로 채워짐
slot.place_urlstring|null정규 모바일 플레이스 URL
slot.labelstring|null그룹 라벨
slot.categorystring업종 키. 조회로 판별된 값으로 갱신되며 항상 값이 있습니다(판별 실패 시 place)
slot.last_rankint|null이번 조회 순위로 갱신
slot.last_review_countint|null이번 조회 방문자 리뷰 수로 갱신
slot.last_checked_atstring|null확인 시각(ISO 8601, KST). 차단된 경우에도 갱신
slot.history[]array일자별 순위 기록(date·rank, 오름차순). 이번 실행분 포함
DELETE /rank/slots/{id} 슬롯 삭제

슬롯을 삭제합니다. 삭제하면 해당 슬롯의 순위 이력도 함께 사라집니다. 활성 슬롯을 삭제하면 사용량(used)이 즉시 줄지만, 자동 중단된 슬롯은 애초에 used에 포함되지 않으므로 삭제해도 used는 그대로입니다. 되돌릴 수 없습니다.

요청 파라미터
파라미터필수타입설명
id필수int경로 파라미터 — 슬롯 ID. 내 소유가 아니면 403, 없는 ID면 404
요청 예시
curl -X DELETE https://ops-388a48cadf.rankfree.co.kr/api/v1/rank/slots/128 \
  -H "Authorization: Bearer rk_..."
응답 예시
{
  "deleted": true
}
응답 필드
필드타입설명
deletedbool삭제 완료 여부(항상 true)
POST /rank/check 1회성 순위 조회

슬롯을 만들지 않고 키워드 × 플레이스 순위를 한 번만 조회합니다. 슬롯 한도를 소모하지 않고 기록도 남지 않습니다. 광고를 제외한 오가닉 순위이며 좌표는 서울 고정입니다. 업종 전용 리스트에서 미노출이면 통합 플레이스 리스트로 1회 다시 조회하는데, 이 폴백은 플레이스 ID를 확정했고 판별된 업종이 place가 아니며 차단되지 않은 경우에만 동작합니다. ID를 못 찾아 업체명 부분일치로 찾는 경우에는 폴백하지 않습니다.

요청 파라미터
파라미터필수타입설명
keyword필수string검색 키워드(최대 100자)
place필수string플레이스 URL 또는 ID(최대 1000자). ID를 추출하지 못하면 입력값을 업체명으로 보고 리스트에서 부분일치로 찾습니다(이 경우 통합 리스트 폴백 없음)
요청 예시
curl -X POST https://ops-388a48cadf.rankfree.co.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.blockedbool네이버 조회 차단(429/405 · 토큰 만료) 여부. true면 순위는 신뢰할 수 없으니 잠시 뒤 재시도
result.foundbool상위 300위 리스트에서 대상 업체를 찾았는지 여부
result.rankint순위(1~). 300=리스트 내 미노출(300위 밖은 언제나 이 값), 0=차단(blocked: true)이거나 조회 불가(키워드·대상 미확정 · 릴레이 폴백 결과 0)
result.list_totalint해당 키워드 검색 결과의 총 업체 수
result.categorystring조회에 사용한 업종 키(hairshop·restaurant·place 등)
result.place_idstring매칭된 플레이스 ID. 못 찾으면 요청에서 확정한 ID 또는 빈 문자열
result.place_namestring매칭된 업체명. 못 찾으면 빈 문자열
result.review_countint|null방문자 리뷰 수
result.blog_review_countint|null블로그·카페 리뷰 수
result.save_countint|null저장 수
result.review_scorefloat|null방문자 리뷰 평점
result.tagsarray업체 대표 태그(문자열 배열). 없으면 빈 배열

경쟁분석 scope: compete

순위추적 슬롯(키워드 x 플레이스)을 기준으로 네이버 플레이스 상위 노출 경쟁 업체를 수집해, 내 플레이스와 리뷰·평점·사진·정보충실성 등의 신호를 비교·점수화합니다. 분석 실행(POST)은 수집을 동기로 처리해 수십 초가 걸리며, 결과는 하루 1건의 일자별 스냅샷으로 저장되어 이후 조회(GET)로 언제든 다시 읽을 수 있습니다. 점수 N1/N2/N3·D1~D10랭크프리가 관측 신호로 산출한 자체 추정치이며 네이버가 공개하는 공식 지표가 아닙니다.

항목규칙
인증Authorization: Bearer rk_... 헤더(또는 X-API-KEY). compete 권한이 있는 API 키만 호출할 수 있습니다
Base URLhttps://ops-388a48cadf.rankfree.co.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_rank300
오류401 키 무효·만료 / 403 scope 없음·타인 슬롯 / 404 존재하지 않는 slotId(경로의 슬롯은 라우트 모델 바인딩이라 소유 검사보다 먼저 404 가 납니다) / 429 일일 호출 한도 초과 또는 네이버 조회 차단
GET /compete/tracks 슬롯별 최신 분석 요약

내 순위추적 슬롯 전체와 각 슬롯의 가장 최근 분석 결과(N1/N2/N3·순위)를 한 번에 가져옵니다. 요청 파라미터는 없습니다. 아직 한 번도 분석하지 않은 슬롯은 analyzed_ymd 와 점수 필드가 모두 null 로 내려옵니다. 다른 엔드포인트에 쓰는 slotId 는 여기의 slot_id 값입니다.

요청 예시
curl https://ops-388a48cadf.rankfree.co.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_idint슬롯 ID — 이후 호출의 slotId
tracks[].keywordstring추적 키워드
tracks[].place_idstring네이버 플레이스 ID
tracks[].place_namestring업체명
tracks[].categorystring업종 경로 — place·restaurant·hairshop·nailshop·hospital·accommodation
tracks[].analyzed_ymdstring|null최신 분석일(YYYY-MM-DD). 분석 이력이 없으면 null
tracks[].n1string|null키워드 일치 점수(0~100, 자체 추정치). 저장값 그대로라 소수 3자리 문자열
tracks[].n2string|null경쟁력 종합 점수(0~100, 자체 추정치). 소수 3자리 문자열
tracks[].n3string|null순위 환산 점수(0~100, 자체 추정치). 소수 3자리 문자열
tracks[].rnkint|null최신 분석 시점의 내 플레이스 순위. 300 = 상위 300위 밖(미노출)
GET /compete/{slotId} 최신 분석 상세(경쟁셋·해설·추이)

해당 슬롯의 가장 최근 분석일 스냅샷을 반환합니다. 경쟁셋 비교표(rows), 내 플레이스 점수 해설(explain), 일자별 추이(series)로 구성됩니다. rows 는 내 플레이스가 맨 앞에 오고 그다음 순위 오름차순입니다. 분석 이력이 없으면 analyzed: false 로만 응답하므로, 먼저 POST /compete/{slotId}/analyze 를 실행해야 합니다.

요청 파라미터
파라미터필수타입설명
slotId필수int경로 파라미터. 순위추적 슬롯 ID(없는 ID 는 404, 본인 소유가 아니면 403)
요청 예시
curl https://ops-388a48cadf.rankfree.co.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
}
응답 필드
필드타입설명
analyzedbool분석 이력 유무. falserows 는 빈 배열, mine·explainnull 만 내려옵니다
ymdstring스냅샷 기준일(YYYY-MM-DD) — 가장 최근 분석일
keywordstring슬롯 키워드
place_namestring내 플레이스 업체명
rows[]array해당 일자 경쟁셋 — 상위 30개. 내 플레이스가 상위 30 밖이면 내 플레이스 행이 따로 한 건 더 저장되어 최대 31행이 됩니다. 내 플레이스 우선 → 순위 오름차순
rows[].rnkint노출 순위(1~). 300 = 상위 300위 밖
rows[].namestring업체명
rows[].place_idstring네이버 플레이스 ID
rows[].is_minebool내 플레이스 여부
rows[].visitor_cntint|null방문자 리뷰 수
rows[].blog_cntint|null블로그·카페 리뷰 수
rows[].review_scorestring|null평점(5점 만점). 저장값 그대로라 소수 2자리 문자열("4.87")
rows[].d7string|null정보충실성 점수(0~100, 소수 3자리 문자열). 상세 수집한 업체만 값이 있습니다
rows[].n1string|null키워드 일치 점수(자체 추정치, 소수 3자리 문자열)
rows[].n2string|null경쟁력 종합 점수(자체 추정치, 소수 3자리 문자열)
rows[].n3string|null순위 환산 점수(자체 추정치, 소수 3자리 문자열)
rows[].tierint|null1 = 목록 지표만 수집 / 2 = 상세까지 수집(상위 max(detail_top, 10) 이내 또는 내 플레이스)
explainobject|null내 플레이스 점수 해설. 내 플레이스의 상세 수집분이 없으면 null
explain.componentsobjectN1 구성요소 — 키워드를 지역어/업종어로 분해해 매칭한 결과
explain.components.Lfloat|null지역 일치 0~1 (주소 1.0 / 상호 0.7 / 대표키워드 0.5 / 불일치 0)
explain.components.Bfloat|null업종 일치 0~1 (업종 경로 또는 카테고리명 일치 1.0 / 동일 계열 0.8)
explain.components.Tfloat|null대표키워드 일치 0~1 (전체 키워드 1.0 / 업종어 0.6 / 지역 핵심어 0.4)
explain.components.Mfloat|null상호 일치 0~1 (지역 핵심어 포함 1.0 / 업종어 포함 0.6)
explain.components.regionstring키워드에서 분리한 지역어
explain.components.corestring지역 핵심어(역·동·구 등 접미 제거)
explain.components.biztermstring키워드에서 분리한 업종어
explain.seo[]arrayD7 정보충실성 체크리스트 항목
explain.seo[].labelstring항목명 — 메뉴/시술·대표키워드·찾아오는길·대표사진·영업시간 공개·예약 연결·가격 공개·필수정보 완성·톡톡/챗봇·스타일리스트·편의시설·부가 카테고리·결제수단
explain.seo[].rawstring현재 상태 표시값("24개", "공개", "누락 없음" 등)
explain.seo[].gradefloat항목 달성도 0~1
explain.seo[].wfloat항목 가중치(0.5~1.5)
explain.seo[].availint1 = 이 업종에 해당(D7 에 반영) / 0 = 미해당(계산에서 제외)
explain.dimsobject|null내 플레이스의 저장된 점수 원본 — d1~d10·n1·n2·n3. DB 저장값 그대로라 각 값은 소수 3자리 문자열(미산출 항목은 null)
explain.review_kwobject|null방문자 리뷰 AI 분석 키워드 — menus·themes·voted 각각 [{"l": 라벨, "c": 건수}]
explain.review_weeklyobject|null주별 리뷰 누적 — v(방문자)·b(블로그) 각 4칸 배열, 순서대로 최근 1·2·3·4주 누적 건수
explain.review_qualityobject|null최근 4주 방문자 리뷰 품질 지표
explain.review_quality.photo_nint사진 첨부 리뷰 수
explain.review_quality.photo_totalint집계 대상 리뷰 수(최근 4주)
explain.review_quality.photo_ratiofloat사진 첨부 비율 0~1
explain.review_quality.ctxobject방문 맥락별 건수({"예약 후 이용": 14} 형태, 건수 내림차순)
explain.review_quality.authorityobject리뷰어 영향력 — 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 가중산출 기준
d10.18방문자 리뷰 수 — 경쟁셋 90분위 기준 로그 정규화
d20.09블로그·카페 리뷰 수
d30.07예약자 리뷰 수. 미제공 업종은 리뷰 방문맥락 "예약 후 이용" 건수로 대체
d40.12평점 — 방문자 수로 보정한 베이지안 평균
d50.08저장 수 — 음식점(restaurant)만 산출, 그 외 업종은 null
d60.08등록 사진 수
d70.14정보충실성 — explain.seo 체크리스트 가중평균
d8-키워드 일치 — L .30 / B .30 / T .30 / M .10. n1 과 동일 값
d90.20최근 리뷰 유입 — 최근 4주 누적 방문자+블로그 리뷰 수
d100.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
POST /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://ops-388a48cadf.rankfree.co.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 429)
HTTP/1.1 429 Too Many Requests

{
  "message": "조회 제한(nCaptcha 토큰 재발급 필요)",
  "blocked": true
}

네이버가 순위 조회를 차단하면 위 응답이 내려오며 이 호출로 저장된 데이터는 없습니다. 잠시 뒤(수 분 이상 간격) 재시도하세요. 같은 429 라도 blocked 키 없이 daily_limit·used 가 오면 API 키의 일일 호출 한도 초과입니다.

응답 필드
필드타입설명
blockedbool정상 응답(200)에서는 항상 false. 차단은 429 + blocked: true
my_rankint내 플레이스 순위(1~). 상위 300위 안에서 발견되지 않으면 300(저장되는 rnk300). 슬롯에 플레이스가 지정되지 않은 경우에도 300 이며, 0 은 내려가지 않습니다
totalint해당 키워드의 네이버 검색 결과 전체 업체 수
competitorsint이번에 수집한 상위 목록 개수(최대 30). 내 플레이스가 상위 30 밖이라 따로 수집·저장된 행은 포함하지 않습니다
my_scoreobject|null내 플레이스 점수. 슬롯에 플레이스가 지정되지 않았으면 null. 계산 직후 값이라 각 점수는 문자열이 아닌 숫자입니다
my_score.d1 ~ d10float|null세부 지표(0~100). 수집 불가 항목은 null — 위 "점수 지표 정의" 참고
my_score.n1float|null키워드 일치 점수(자체 추정치)
my_score.n2float|null경쟁력 종합 점수(자체 추정치)
my_score.n3float순위 환산 점수(자체 추정치). 순위가 미노출이면 rnk 300 으로 계산되어 약 0.058 이 되며, null 은 나오지 않습니다
my_score.actnull예약 필드 — 현재 항상 null
my_score.maskint산출에 성공한 지표 비트마스크. bit0부터 순서대로 d1·d2·d3·d4·d5·d7·d8·d9·d10·d6 (예: 1007 = d5 만 결측)
my_score.tierint1 = 목록 지표만 / 2 = 상세까지 수집
my_score.rnkint스냅샷에 저장된 순위. 미노출이면 300

키워드분석 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 로 변환해 반환합니다
GET /keyword 경량 — 검색량·경쟁강도·연관 키워드 (scope: keyword)

키워드 1개의 월간 검색량(PC/모바일 분리), 광고 경쟁강도, 연관 키워드 목록을 조회합니다. 연관 키워드는 월간 총 검색량 내림차순으로 정렬되며 개수 제한 없이 전부 반환됩니다.

요청 파라미터
파라미터필수타입설명
keyword필수string분석할 검색 키워드(쿼리스트링). 공백 포함 가능 — 서버가 공백 제거·대문자로 정규화합니다. 빈 값이면 422
요청 예시
curl --get https://ops-388a48cadf.rankfree.co.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
}
응답 필드
필드타입설명
dataobject|null조회 결과. 자격증명 문제·집계 없음이면 null(HTTP 200, message 확인)
data.keywordstring검색광고(keywordstool) 응답의 relKeyword 원문 — 서버 정규화 결과가 아니라 네이버가 돌려준 값이며, 통상 정규화된 형태(공백 제거·영문 대문자)입니다. 정규화 키와 정확히 일치하는 행이 없으면 응답의 첫 연관 행을 대표값으로 사용하므로, 요청한 키워드와 다른 키워드가 반환될 수 있습니다
data.monthly_pcint최근 30일 PC 검색수. 절사값(< 10)은 5
data.monthly_mobileint최근 30일 모바일 검색수
data.comp_idxstring|null광고 경쟁강도 — 높음 / 중간 / 낮음
data.monthly_totalintmonthly_pc + monthly_mobile
data.relatedarray연관 키워드 목록. monthly_total 내림차순, 개수 제한 없음(수십~수백 건)
data.related[].keywordstring연관 키워드(정규화 형태)
data.related[].monthly_pcint연관 키워드의 PC 검색수
data.related[].monthly_mobileint연관 키워드의 모바일 검색수
data.related[].comp_idxstring|null연관 키워드의 경쟁강도
data.related[].monthly_totalint연관 키워드의 월간 총 검색수
messagestring|nulldatanull 일 때 사유 문구. 정상 조회 시 null
월 한도 초과 응답 (429)

회원 기능 한도(keyword_analysis)를 모두 사용한 경우입니다. 두 엔드포인트 공통이며, 다음 달 1일에 초기화됩니다.

HTTP/1.1 429 Too Many Requests
{
  "data": null,
  "limit_exceeded": true,
  "message": "이번 달 키워드 분석 호출 한도(300회)를 초과했습니다."
}
GET /keyword/detail 상세 — 경량 + 성별·연령·트렌드 (scope: keyword_detail)

경량 지표에 더해 성별·연령 분포(성별×연령 버킷 포함), 최근 12개월 검색량 트렌드, 요일별 검색 비율, 자동 인사이트, 검색량 등급(S~F)을 반환합니다. 조회에 성공하면 공개 공유용 share_token 도 함께 발급됩니다. 검색량(경량)과 상세는 소스가 서로 달라 한쪽만 실패할 수 있으며, 검색량만 실패하면 검색량 계열 필드가 응답에서 빠집니다(아래 data 설명 참고). 상세 소스는 별도 세션 기반이라 일시 장애 시 503 이 반환될 수 있습니다.

요청 파라미터
파라미터필수타입설명
keyword필수string분석할 검색 키워드(쿼리스트링). 공백 포함 가능 — 서버가 공백 제거·대문자로 정규화합니다. 빈 값이면 422
요청 예시
curl --get https://ops-388a48cadf.rankfree.co.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
}
응답 필드
필드타입설명
dataobject|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.gradestring|null검색량 등급(자체 추정) — S(10만↑)·A(3만↑)·B(1만↑)·C(3천↑)·D(1천↑)·E(100↑)·F. 검색량 조회 실패 시 null
data.weekdayarray|null최근 90일 요일별 검색 비율(월~일 7개). 검색량 조회가 실패하면 데이터랩과 무관하게 항상 null(요일 조회 자체를 시도하지 않음). 검색량이 성공해도 데이터랩 조회가 불가하면 null
data.weekday[].wstring요일 — ······
data.weekday[].pctfloat해당 요일 비중(%). 7개 합이 100
data.detailobject|null성별·연령·트렌드 묶음. 해당 키워드의 상세 집계가 없으면 null(HTTP 200 + message)
data.detail.gender.femaleint여성 검색수 합계(PC+모바일)
data.detail.gender.maleint남성 검색수 합계(PC+모바일)
data.detail.gender.female_pctfloat여성 비중(%), 소수 1자리
data.detail.gender.male_pctfloat남성 비중(%), 소수 1자리
data.detail.agearray연령대별 집계(최대 7개 밴드)
data.detail.age[].agestring연령 밴드 — 0-12·13-19·20-24·25-29·30-39·40-49·50-
data.detail.age[].totalint해당 연령대 검색수(PC+모바일)
data.detail.age[].pctfloat해당 연령대 비중(%), 소수 1자리
data.detail.monthlyarray최근 12개월 검색량 트렌드(과거 → 최근 순)
data.detail.monthly[].labelstring월 라벨 — YYYY-MM 형식
data.detail.monthly[].pcint해당 월 PC 검색수
data.detail.monthly[].mobileint해당 월 모바일 검색수
data.detail.monthly[].totalint해당 월 총 검색수(pc+mobile)
data.detail.bucketsarray성별×연령 교차 버킷(최대 14개) — 원본 분포를 그대로 쓰고 싶을 때 사용
data.detail.buckets[].genderstringf(여성) / m(남성)
data.detail.buckets[].agestring연령 밴드(age[].age 와 동일 코드)
data.detail.buckets[].pcint해당 버킷 PC 검색수
data.detail.buckets[].mobileint해당 버킷 모바일 검색수
data.detail.buckets[].totalint해당 버킷 총 검색수(pc+mobile)
data.detail.insightsobject|null데이터 기반 자동 요약(시즌성·주 타겟). 성별·연령·월별이 모두 비어 있으면 null
data.detail.insights.cardsarray지표 카드 목록
data.detail.insights.cards[].groupstring묶음 — season(시즌성·성수기·비수기) / target(성별·연령·핵심 타겟)
data.detail.insights.cards[].labelstring카드 제목(예: 시즌성, 성수기, 주 타겟 연령)
data.detail.insights.cards[].valuestring표시 문구(예: 뚜렷함, 4월·5월, 여성 72%)
data.detail.insights.cards[].colorstring강조 색 CSS 변수 문자열(예: var(--color-accent)). 자체 UI 사용 시 무시해도 됩니다
data.detail.insights.summarystring한 문단 자연어 요약(성별·연령·시즌성)
share_tokenstring|null공개 공유 토큰. https://ops-388a48cadf.rankfree.co.kr/keyword/{share_token} 로 로그인 없이 리포트를 열 수 있습니다
messagestring|null상세 데이터가 없을 때 사유 문구. 정상 조회 시 null
소스 장애 응답 (503)

상세 지표 소스(검색광고 세션)에 연결할 수 없을 때 반환합니다. 데이터 없음(200 + detail: null)과 구분되는 일시 장애이므로, 잠시 후 같은 요청을 재시도하면 됩니다.

HTTP/1.1 503 Service Unavailable
{
  "data": null,
  "message": "상세 분석 소스에 일시적으로 연결할 수 없습니다. 잠시 후 다시 시도하세요."
}

마케팅 상품 주문 scope: order

판매 중인 마케팅 상품을 조회하고, 외부 시스템에서 바로 주문을 접수하고, 주문 상태를 확인합니다. 검증·금액 계산은 웹 주문과 완전히 동일한 로직(OrderPlacer)을 공유하므로 화면 주문과 결과가 같습니다. 주문은 pending(접수) 상태로 생성되며, 주문에 쓰는 product_idGET /products 응답의 id입니다.

공통 주문 규칙 — 아래 규칙은 POST /orders 전체에 적용됩니다. 상품마다 다르므로 주문 전 GET /products/{id} 로 스펙을 먼저 확인하세요.

항목규칙
quantity 수량. 상품 상세의 fieldsdaily_qty 필드가 있으면 본문의 quantity 는 무시되고 fields.daily_qty 값이 수량이 됩니다. 그 필드가 없을 때만 quantity 를 사용하며, 미전달 시 0 으로 평가됩니다. min_quantity ~ max_quantity 범위를 벗어나면 422
days quantity_modedaily 인 상품에만 의미가 있습니다. 상품에 start_dateend_date 필드가 둘 다 있을 때만 days 가 무시되고 종료일 − 시작일 + 1 로 계산됩니다. 둘 다 갖춰지지 않은 상품(두 필드가 모두 없거나 start_date 만 있는 경우 등)은 days 를 쓰며, 미전달 시 min_days 가 적용됩니다. quantity_modetotal 이면 일수는 1로 취급되고 응답 daysnull
fixed_quantity 값이 있는 고정 수량(패키지) 상품quantity·fields.daily_qty 로 무엇을 보내든 고정값으로 접수됩니다. 저장되는 daily_qty 값도 고정값으로 덮어써집니다
fixed_days quantity_modedaily 인 상품에서만 적용됩니다. 이때 값이 있는 고정 기간 상품days 를 보내도 고정 일수로 접수되고 min_days 검증도 건너뜁니다. start_date 필드가 있으면 시작일만 보내면 되고(누락 시 422 field: "days"), end_date 필드까지 있으면 종료일은 시작일 + 고정일수 − 1 로 서버가 재계산해 제출값을 덮어씁니다. quantity_modetotal 이면 fixed_days무시되고 일수 1·응답 daysnull
날짜 필드 DATE 타입은 YYYY-MM-DD 문자열. 상품의 earliest_start_date(접수 마감·진행 지연·주말 반영) 보다 이른 날짜는 422
URL 필드 URL 타입의 플레이스 주소는 서버가 표준 m.place 주소로 정규화해 저장합니다(응답 fields 값이 보낸 값과 다를 수 있음)
contains 필드 스펙의 contains 문자열이 입력값에 포함돼 있지 않으면 422 f_{필드키}. 정규화가 끝난 뒤에 검사합니다
not_contains 관리자가 지정한 금지 문자열이 입력값에 포함돼 있으면 contains 와 동일하게 422 f_{필드키}(정규화 후 검사). 다만 이 규칙은 필드 스펙 응답(GET /products/{id})에 내려가지 않습니다 — 값이 있는지는 오류 메시지로만 확인할 수 있으니 운영자에게 확인하세요
숨김(내부) 필드 상품 상세의 fields 에는 고객 입력 항목과 운영자 전용 숨김 필드가 함께 내려오지만, 응답에는 이를 구분할 키(is_hidden 등)가 없습니다. 숨김 필드는 required: true 여도 누락으로 422 가 나지 않고, 보낸 값은 무시된 채 서버 기본값(없으면 null)으로 저장됩니다(실값은 수집·관리자 입력으로 채워짐). 어떤 키가 숨김인지는 운영자에게 확인하세요
파일 필드 FILE·IMAGE 타입은 API 미지원(api_supported: false). 필수 파일 필드가 있는 상품은 목록에서 orderable: false 로 내려오고, 주문을 시도하면 422 field: "product_id" 로 거절됩니다(웹 주문만 가능)
user_coupon_id 보유 쿠폰 발급분 ID(선택). 본인 발급분·사용 가능·해당 상품 적용 가능 여부를 검증한 뒤 할인 금액을 서버가 재계산discount_amount·total_price 에 반영합니다
금액 total_price = unit_price × quantity × days − discount_amount (0 미만이면 0). 모든 금액은 원 단위 정수
GET /products 주문 가능 상품 목록

판매 중(활성)인 마케팅 상품을 제목 오름차순으로 모두 반환합니다. 쿼리 파라미터는 없습니다. 여기서 얻은 idPOST /ordersproduct_id 로 사용하며, orderablefalse 인 상품은 API 로 주문할 수 없습니다.

요청 예시
curl https://ops-388a48cadf.rankfree.co.kr/api/v1/products \
  -H "Authorization: Bearer rk_..."
응답 예시
{
  "products": [
    {
      "id": 12,
      "title": "네이버 플레이스 저장",
      "type": "REWARD",
      "type_name": "참여형 리워드",
      "unit_price": 200,
      "quantity_mode": "daily",
      "min_quantity": 10,
      "max_quantity": 10000,
      "min_days": 1,
      "fixed_quantity": null,
      "fixed_days": null,
      "earliest_start_date": "2026-07-31",
      "orderable": true,
      "not_orderable_reason": null
    },
    {
      "id": 27,
      "title": "블로그 체험단 10인 패키지",
      "type": "EXPERIENCE",
      "type_name": "체험단",
      "unit_price": 90000,
      "quantity_mode": "total",
      "min_quantity": 1,
      "max_quantity": 50,
      "min_days": 1,
      "fixed_quantity": 10,
      "fixed_days": null,
      "earliest_start_date": "2026-08-03",
      "orderable": false,
      "not_orderable_reason": "필수 파일 첨부 필드: 제품 이미지"
    }
  ]
}
응답 필드
필드타입설명
products[].idint상품 번호 — 주문 시 product_id 로 사용(관리자 상품 목록의 번호와 동일)
products[].titlestring상품명
products[].typestring상품 유형 코드(REWARD·EXPERIENCE·SNS·BLOG_REVIEW·REVIEW 등)
products[].type_namestring상품 유형 이름(한글)
products[].unit_priceint단가(원)
products[].quantity_modestring과금 방식 — daily(단가×일수량×일수) 또는 total(단가×수량)
products[].min_quantityint최소 수량
products[].max_quantityint최대 수량
products[].min_daysint최소 기간(일)
products[].fixed_quantityint|null값이 있으면 수량 고정(입력 무시)
products[].fixed_daysint|null값이 있으면 기간 고정(종료일 자동 계산). quantity_mode: "daily" 상품에서만 적용
products[].earliest_start_datestring선택 가능한 가장 빠른 시작일(YYYY-MM-DD)
products[].orderableboolfalse = 필수 파일 첨부 필드가 있어 API 주문 불가
products[].not_orderable_reasonstring|null주문 불가 사유(파일 필드 라벨). 주문 가능하면 null
GET /products/{id} 상품 상세 · 주문 필드 스펙

목록 응답의 모든 필드에 더해 description주문 입력 필드 스펙(fields) 을 반환합니다. POST /ordersfields 객체는 여기 나온 key 를 그대로 키로 사용합니다. 다만 fields 에는 고객 입력 항목뿐 아니라 운영자 전용 숨김(내부) 필드도 같은 모양으로 섞여 내려오고, 응답만으로는 이를 구분할 수 없습니다(숨김 필드는 값을 보내도 무시되고 required 검증도 하지 않습니다 — 위 공통 주문 규칙 참고). 판매 중이 아니거나 없는 상품이면 404 입니다.

요청 파라미터
파라미터필수타입설명
id필수int경로 파라미터 — 상품 번호(GET /productsid)
요청 예시
curl https://ops-388a48cadf.rankfree.co.kr/api/v1/products/12 \
  -H "Authorization: Bearer rk_..."
응답 예시
{
  "product": {
    "id": 12,
    "title": "네이버 플레이스 저장",
    "type": "REWARD",
    "type_name": "참여형 리워드",
    "unit_price": 200,
    "quantity_mode": "daily",
    "min_quantity": 10,
    "max_quantity": 10000,
    "min_days": 1,
    "fixed_quantity": null,
    "fixed_days": null,
    "earliest_start_date": "2026-07-31",
    "orderable": true,
    "not_orderable_reason": null,
    "description": "네이버 플레이스 저장(즐겨찾기)을 일 단위로 진행합니다.",
    "fields": [
      {
        "key": "place_url",
        "label": "플레이스 URL",
        "type": "URL",
        "required": true,
        "help": "네이버 지도에서 업체를 검색한 뒤 주소를 복사해 주세요.",
        "options": null,
        "contains": "place.naver.com",
        "api_supported": true
      },
      {
        "key": "daily_qty",
        "label": "일 수량",
        "type": "NUMBER",
        "required": true,
        "help": null,
        "options": null,
        "contains": null,
        "api_supported": true
      },
      {
        "key": "start_date",
        "label": "시작일",
        "type": "DATE",
        "required": true,
        "help": null,
        "options": null,
        "contains": null,
        "api_supported": true
      },
      {
        "key": "end_date",
        "label": "종료일",
        "type": "DATE",
        "required": true,
        "help": null,
        "options": null,
        "contains": null,
        "api_supported": true
      }
    ]
  }
}
응답 필드
필드타입설명
product.*-GET /products 의 상품 필드 전부 동일하게 포함
product.descriptionstring|null상품 설명(상세 조회에만 포함)
product.fields[].keystring주문 시 fields 객체에 쓰는 키(daily_qty·start_date·end_date 는 수량·기간 시스템 필드). 숨김(내부) 필드의 키는 보내도 무시됩니다
product.fields[].labelstring필드 이름(오류 메시지에 그대로 등장)
product.fields[].typestringTEXT·TEXTAREA·URL·NUMBER·SELECT·MULTI_SELECT·TOGGLE·DATE·FILE·IMAGE·ADDRESS·MISSION_OPTIONS·TAGS
product.fields[].requiredbool필수 여부. 누락 시 422 f_{필드키}. 단 숨김(내부) 필드는 예외true 여도 검증하지 않고 보낸 값도 무시되며, 응답만으로는 숨김 여부를 알 수 없습니다(기본값이 true 라 숨김 + required: true 조합이 흔합니다)
product.fields[].helpstring|null입력 도움말
product.fields[].optionsarray|nullSELECT·MULTI_SELECT{value, label} 목록. 그 외에는 null
product.fields[].containsstring|null입력값에 반드시 포함돼야 하는 문자열(위반 시 422). 반대 규칙인 금지 문자열(not_contains)은 이 응답에 포함되지 않습니다
product.fields[].api_supportedboolfalse = FILE·IMAGE 필드로 API 로는 값을 보낼 수 없음
POST /orders 주문 생성 (분당 30회)

상품을 주문 접수합니다. 성공 시 201 Created 로 응답하며, 생성된 주문은 pending(접수) 상태이고 운영자 승인 후 진행됩니다. 상품별 입력 필드는 fields 객체에 {"필드키": "값"} 형태로 담습니다(값이 배열이면 필드 타입과 무관하게 문자열 배열로 받습니다 — MULTI_SELECT·TAGS·MISSION_OPTIONS 등. 배열 안의 비스칼라 원소는 버려집니다). 위 공통 주문 규칙이 그대로 적용됩니다. 요청 제한은 분당 30회입니다.

요청 파라미터
파라미터필수타입설명
product_id필수int상품 번호. 없거나 판매 중이 아니면 404
quantity선택int수량(1 이상). 상품에 daily_qty 필드가 있으면 무시되고 fields.daily_qty 가 쓰임. fixed_quantity 상품은 고정값으로 강제. daily_qty 필드도 fixed_quantity 도 없는 상품에서는 사실상 필수 — 미전달 시 0 으로 평가돼 422 field: "quantity"
days선택int기간(일, 1 이상). 상품에 start_date·end_date 필드가 둘 다 있으면 무시되고 날짜로 계산. 미전달 시 min_days. fixed_days 상품(quantity_mode: "daily")은 고정값으로 강제
fields선택object상품 상세의 fields 스펙대로 필드키 → 값. 필수 필드가 있는 상품에서는 사실상 필수
user_coupon_id선택int사용할 보유 쿠폰 발급분 ID. 할인은 서버가 재계산
요청 예시
curl -X POST https://ops-388a48cadf.rankfree.co.kr/api/v1/orders \
  -H "Authorization: Bearer rk_..." \
  -H "Content-Type: application/json" \
  -d '{"product_id": 12, "fields": {"place_url": "https://m.place.naver.com/restaurant/1234567890", "daily_qty": "100", "start_date": "2026-08-01", "end_date": "2026-08-07"}}'
응답 예시 (201 Created)
{
  "order": {
    "order_no": "MO2607291A2B3C",
    "status": "pending",
    "status_label": "접수",
    "product": {
      "id": 12,
      "title": "네이버 플레이스 저장"
    },
    "quantity": 100,
    "days": 7,
    "unit_price": 200,
    "discount_amount": 0,
    "total_price": 140000,
    "fields": {
      "place_url": "https://m.place.naver.com/restaurant/1234567890",
      "daily_qty": "100",
      "start_date": "2026-08-01",
      "end_date": "2026-08-07"
    },
    "created_at": "2026-07-29T14:30:00+09:00"
  }
}
응답 필드
필드타입설명
order.order_nostring주문번호 — GET /orders/{orderNo} 조회 키
order.statusstringpending·processing·completed·canceled. 생성 직후는 항상 pending
order.status_labelstring상태 한글 표기(접수·진행중·완료·취소)
order.product.idint|null주문 상품 번호
order.product.titlestring|null주문 상품명
order.quantityint서버가 확정한 수량(고정 수량 상품이면 고정값)
order.daysint|null서버가 확정한 기간(일). quantity_mode: "total" 상품은 null(fixed_days 가 있어도 무시)
order.unit_priceint주문 시점 단가(원)
order.discount_amountint쿠폰 할인액(원). 쿠폰 미사용이면 0
order.total_priceint최종 결제 금액(원) = 단가 × 수량 × 일수 − 할인액
order.fieldsobject실제 저장된 입력값(URL 정규화·고정값 덮어쓰기·종료일 재계산·숨김 필드 기본값이 반영된 값). 값이 없으면 빈 객체
order.created_atstring접수 일시(ISO 8601, KST)
오류 응답
코드field상황
404product_id상품이 없거나 판매 중이 아님
422product_id필수 파일 첨부 필드가 있는 상품(API 주문 불가)
422f_{필드키}필수 필드 누락, earliest_start_date 이전 날짜, contains 불일치, not_contains(금지 문자열) 포함 등 동적 필드 검증 실패
422quantity수량이 min_quantity ~ max_quantity 범위 밖(quantity 미전달로 0 인 경우 포함)
422days시작일·종료일 누락, 종료일이 시작일보다 이전, 최소 기간 미달
422user_coupon_id사용 불가 쿠폰(만료·사용됨·중지), 상품 미적용, 최소 주문 금액 미달
{
  "message": "'플레이스 URL' 항목을 입력하세요.",
  "field": "f_place_url"
}
GET /orders 내 주문 목록

API 키 소유 계정의 주문을 최신순으로 반환합니다. 상태 필터와 페이지네이션을 지원하며, 각 항목의 구조는 POST /ordersorder 와 동일합니다.

요청 파라미터
파라미터필수타입설명
status선택stringpending·processing·completed·canceled 중 하나. 알 수 없는 값은 무시되고 전체가 조회됨
per_page선택int페이지당 건수. 기본 20, 1~100 으로 자동 보정
page선택int페이지 번호. 기본 1
요청 예시
curl "https://ops-388a48cadf.rankfree.co.kr/api/v1/orders?status=processing&per_page=20&page=1" \
  -H "Authorization: Bearer rk_..."
응답 예시
{
  "orders": [
    {
      "order_no": "MO2607291A2B3C",
      "status": "processing",
      "status_label": "진행중",
      "product": {
        "id": 12,
        "title": "네이버 플레이스 저장"
      },
      "quantity": 100,
      "days": 7,
      "unit_price": 200,
      "discount_amount": 10000,
      "total_price": 130000,
      "fields": {
        "place_url": "https://m.place.naver.com/restaurant/1234567890",
        "daily_qty": "100",
        "start_date": "2026-08-01",
        "end_date": "2026-08-07"
      },
      "created_at": "2026-07-29T14:30:00+09:00"
    },
    {
      "order_no": "MO260728D4E5F6",
      "status": "processing",
      "status_label": "진행중",
      "product": {
        "id": 27,
        "title": "블로그 체험단 10인 패키지"
      },
      "quantity": 10,
      "days": null,
      "unit_price": 90000,
      "discount_amount": 0,
      "total_price": 900000,
      "fields": {
        "shop_name": "연남동 소금빵집",
        "guide_note": "매장 외관 사진 필수"
      },
      "created_at": "2026-07-28T10:05:12+09:00"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 20,
    "total": 37,
    "last_page": 2
  }
}
응답 필드
필드타입설명
orders[]array주문 배열 — 항목 구조는 POST /ordersorder 와 동일(order_no·status·status_label·product·quantity·days·unit_price·discount_amount·total_price·fields·created_at)
meta.pageint현재 페이지 번호
meta.per_pageint페이지당 건수(보정된 실제 값)
meta.totalint필터 적용 후 전체 주문 수
meta.last_pageint마지막 페이지 번호 — page 가 이 값에 도달하면 순회 종료
GET /orders/{orderNo} 주문 단건 조회 · 상태 확인

주문번호로 본인 주문 한 건을 조회합니다. 진행 상태(status) 확인용으로 사용하세요. 다른 계정의 주문이거나 없는 주문번호면 404 입니다.

요청 파라미터
파라미터필수타입설명
orderNo필수string경로 파라미터 — 주문번호(order.order_no)
요청 예시
curl https://ops-388a48cadf.rankfree.co.kr/api/v1/orders/MO2607291A2B3C \
  -H "Authorization: Bearer rk_..."
응답 예시
{
  "order": {
    "order_no": "MO2607291A2B3C",
    "status": "completed",
    "status_label": "완료",
    "product": {
      "id": 12,
      "title": "네이버 플레이스 저장"
    },
    "quantity": 100,
    "days": 7,
    "unit_price": 200,
    "discount_amount": 10000,
    "total_price": 130000,
    "fields": {
      "place_url": "https://m.place.naver.com/restaurant/1234567890",
      "daily_qty": "100",
      "start_date": "2026-08-01",
      "end_date": "2026-08-07"
    },
    "created_at": "2026-07-29T14:30:00+09:00"
  }
}
응답 필드
필드타입설명
order.statusstringpending(접수) → processing(진행중) → completed(완료), 또는 canceled(취소)
order.status_labelstring상태 한글 표기
order.*-나머지 필드는 POST /orders 응답과 동일
오류 응답
{
  "message": "주문을 찾을 수 없습니다."
}

쇼핑 유입키워드 scope: shop_keyword

핵심 키워드 하나와 내 상품 URL 로 롱테일 키워드 조합을 자동 생성하고, 각 조합으로 네이버 쇼핑을 검색해 내 상품이 상위 N위 안에 노출되는 키워드만 골라냅니다. 분석을 만들면 순위 확인이 자동으로 끝까지 진행되며, 찾아낸 노출 키워드는 그룹으로 나눠 Short URL 로 바로 발급할 수 있습니다(규칙은 관리자 화면과 동일).

1단계
상품 정보 — 자동 수집

보내는 값은 핵심 키워드와 상품 URL 두 개뿐입니다. 상품 제목 · 상점명 · 가격 같은 상품 정보는 랭크프리가 알아서 수집합니다 — 따로 넣을 값이 없습니다. 수집에는 요청자 계정으로 로그인된 랭크프리 확장 프로그램이 필요하며, 확장이 켜져 있으면 화면을 열어 둘 필요 없이 자동으로 처리됩니다.

2단계
조합 + 순위체크

수집된 상품 정보로 롱테일 키워드를 자동으로 만들어, 키워드마다 쇼핑을 검색해 내 상품이 상위 N위 안에 노출되는지 확인합니다. 이 확인도 확장 프로그램이 이어서 처리합니다. 생성 응답은 확인을 기다리지 않고 바로 돌아오므로, 진행률은 GET /shop-keywords/{id}progress 로 폴링합니다.

3단계
Short URL

노출로 판정된 키워드(exposed_keywords)를 원하는 그룹 수로 나눠 그룹별 단축 URL 을 발급합니다. 발급된 url 과 배정 키워드를 발주 · 배포 시스템이 그대로 가져다 씁니다.

항목공통 규칙
Base URLhttps://ops-388a48cadf.rankfree.co.kr/api/v1 — 아래 모든 경로 앞에 붙습니다.
인증Authorization: Bearer rk_... 헤더. API 키에 shop_keyword 스코프가 있어야 합니다.
소유권키 소유자가 만든 분석만 접근할 수 있습니다. 남의 id 를 조회하면 403.
호출 한도생성 · 변경 계열(POST)은 분당 30회.
statuspending(상품 정보 수집 대기 — 확장이 채우면 자동으로 checking 으로 넘어갑니다) · checking(확인 중) · done(완료) · blocked(차단으로 중단) · paused(사용자 중단)
확장 프로그램상품 정보 수집과 순위 확인요청자 계정으로 로그인된 랭크프리 확장이 담당합니다. 확장이 켜져 있지 않으면 진행되지 않습니다(화면을 열어 둘 필요는 없습니다).
중복 요청같은 키워드 + 같은 상품으로 다시 요청하면 새로 만들지 않고 기존 분석을 그대로 돌려줍니다(응답에 reused: true, 상태코드 200). 새로 생성될 때만 201 입니다.
progresstotal(전체 조합) · checked(확인 완료) · remaining(남은 조합) · exposed(노출 판정 수). remaining 이 0 이면 확인 종료입니다.
POST /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://ops-388a48cadf.rankfree.co.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"
  }'
응답 예시
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 가 비어 있고 statuspending 입니다. 확장이 수집하면 값이 채워지고 checking 으로 넘어갑니다 — 진행은 상세 조회로 폴링하세요.

응답 필드
필드타입설명
analysis.idint분석 ID. 이후 조회 · Short URL 호출의 {id} 입니다.
analysis.core_keywordstring입력한 핵심 키워드
analysis.product_urlstring상품 정보 — 자동 정리된 상품 URL(추적 파라미터 제거). URL 이 아니면 빈 문자열
analysis.product_idstring상품 정보 — URL 에서 자동 추출한 상품 ID(스마트스토어 channelProductId 또는 가격비교 nvMid). 업체 매칭이면 빈 문자열
analysis.mall_namestring상품 정보 — 자동 수집된 상점명(입력이 URL 이 아니면 입력한 업체명). 수집 전에는 빈 문자열
analysis.product_titlestring자동 수집된 상품 제목. 수집 전에는 빈 문자열
analysis.brandstring자동 수집된 브랜드 · 제조사
analysis.product_priceint자동 수집된 판매가(원). 수집 전에는 0
analysis.thresholdint노출 판정 기준 순위
analysis.statusstring진행 상태. 조합이 0개면 즉시 done
analysis.progress.totalint생성된 조합 수
analysis.progress.checkedint순위 확인이 끝난 조합 수
analysis.progress.remainingint남은 조합 수(0 이면 확인 종료)
analysis.progress.exposedint1~threshold 위로 확인된 조합 수
analysis.progress.blockedbool차단으로 확인이 멈췄는지 여부
analysis.created_atstring생성 시각(ISO 8601, KST)
analysis.productobject수집된 상품 정보 — 모든 응답(생성 · 목록 · 상세)에 같은 모양으로 들어갑니다. 생성 직후에는 아직 수집 전이라 빈 값이고, 확장이 채우면 값이 들어옵니다
analysis.product.seller_tagsarray해시태그(관련 태그) — 상품 상세페이지 하단 태그. # 없이 문자열 배열
analysis.product.thumbnail_urlstring대표이미지 URL
analysis.product.title · brand · mall_name · price · category상품 제목 · 브랜드 · 상점명 · 판매가(원) · 카테고리
analysis.product.collected_atstring상품 정보 수집 시각(ISO 8601). 수집 전이면 null
GET /shop-keywords 내 분석 목록

내 API 키로 만든 분석을 최신순으로 조회합니다. 각 항목은 생성 응답과 동일한 analysis 페이로드(진행률 포함)입니다.

요청 파라미터
파라미터필수타입설명
page선택int페이지 번호(기본 1)
per_page선택int페이지당 개수(1~100, 기본 20)
요청 예시
curl "https://ops-388a48cadf.rankfree.co.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 와 동일
pageint현재 페이지
per_pageint페이지당 개수
totalint전체 분석 수
GET /shop-keywords/{id} 진행 상태 · 상품 정보 · 노출 키워드

분석 하나의 진행률 · 저장된 상품 정보 · 노출 판정 키워드 · 발급된 Short URL 을 한 번에 돌려줍니다. 생성 직후에는 progress.remaining 이 0 이 될 때까지 몇 초 간격으로 폴링하면 됩니다.

요청 파라미터
파라미터필수타입설명
id필수int경로 파라미터. 분석 ID(내 소유가 아니면 403)
요청 예시
curl https://ops-388a48cadf.rankfree.co.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.idint분석 ID(요청한 {id} 와 동일)
analysis.product_urlstring상품 정보 — 정리된 상품 URL(쿼리스트링 제거)
analysis.product_idstring상품 정보 — 서버가 URL 에서 자동 추출한 상품 ID
analysis.mall_namestring상품 정보 — 자동 수집된 상점명. 수집 전에는 빈 문자열
analysis.product_title · analysis.brand · analysis.product_pricestring · string · int자동 수집된 상품 제목 · 브랜드 · 판매가
analysis.productobject수집된 상품 정보 전체 — 아래 항목을 담습니다(상세 조회에만 포함)
analysis.product.seller_tagsarray해시태그(관련 태그) — 상품 상세페이지 하단의 태그 목록. # 없이 문자열 배열로 반환합니다
analysis.product.titlestring상품 제목
analysis.product.brandstring브랜드 · 제조사
analysis.product.mall_namestring상점명
analysis.product.priceint판매가(원)
analysis.product.categorystring카테고리
analysis.product.thumbnail_urlstring대표이미지 URL
analysis.product.collected_atstring상품 정보를 수집한 시각(ISO8601). 아직 수집 전이면 null
analysis.core_keywordstring핵심 키워드
analysis.thresholdint노출 판정 기준 순위
analysis.statusstringchecking/done/blocked/paused
analysis.progress.*objecttotal · checked · remaining · exposed · blocked
analysis.created_atstring생성 시각(ISO 8601)
exposed_keywordsarray노출 판정(1~threshold 위) 키워드 문자열 배열. 확인 순서를 유지하고 중복은 제거합니다. 3단계 Short URL 의 재료입니다.
short_links[].group_noint그룹 번호(1부터)
short_links[].urlstring발급된 Short URL(아직 생성 전이면 배열이 비어 있음)
short_links[].keywordsarray이 그룹에 배정된 키워드
short_links[].hit_countint이 링크가 호출된 횟수
POST /shop-keywords/{id}/short-links Short URL 생성

노출 판정 키워드를 group_count 개 그룹으로 나눠 그룹마다 Short URL 을 새로 발급합니다. 기존 링크는 교체되므로, 이미 배포해 호출이 발생한 링크가 있다면 이 엔드포인트 대신 /reassign 을 사용하세요.

요청 파라미터
파라미터필수타입설명
id필수int경로 파라미터. 분석 ID
group_count필수int만들 그룹(=Short URL) 수(1~100). 노출 키워드 수보다 클 수 없습니다.
Short URL 규칙
항목규칙
그룹 분배노출 키워드를 순서대로 그룹에 라운드로빈으로 고르게 나눕니다(1번 → 2번 → … → 1번).
재생성 제한이미 호출된 적이 있는 링크(hit_count > 0)가 하나라도 있으면 생성이 막힙니다 — 배포한 주소가 죽지 않도록 하기 위함입니다. 이때는 /reassign 으로 키워드만 교체하세요.
실패 응답노출 키워드 없음 · 그룹 수 초과 · 호출된 링크 존재는 422 + message + field(group_count)
요청 예시
curl -X POST https://ops-388a48cadf.rankfree.co.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_noint그룹 번호(1부터 오름차순)
short_links[].urlstring발급된 Short URL(보조 도메인이 설정돼 있으면 그룹별로 번갈아 사용)
short_links[].keywordsarray이 그룹에 배정된 노출 키워드
short_links[].hit_countint호출 횟수(생성 직후 0)
messagestring실패(422) 시 사유
fieldstring실패(422) 시 원인 필드 — group_count
GET /shop-keywords/{id}/short-links Short URL 목록

발급된 Short URL 만 가볍게 조회합니다. 발주 · 배포 시스템이 링크와 배정 키워드를 그대로 가져다 쓰거나, hit_count 로 호출량을 확인할 때 사용합니다.

요청 파라미터
파라미터필수타입설명
id필수int경로 파라미터. 분석 ID
요청 예시
curl https://ops-388a48cadf.rankfree.co.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_noint그룹 번호
short_links[].urlstringShort URL
short_links[].keywordsarray현재 배정된 키워드
short_links[].hit_countint누적 호출 횟수
POST /shop-keywords/{id}/short-links/reassign 키워드만 재배정(URL 유지)

이미 배포한 Short URL 주소는 그대로 두고 배정 키워드만 다시 나눕니다. 순위 확인이 더 진행돼 노출 키워드가 늘었을 때 사용합니다(생성은 호출된 링크가 있으면 막히므로 이쪽을 쓰세요). 요청 본문은 필요 없고, 그룹 수는 기존 링크 수를 그대로 유지합니다.

요청 파라미터
파라미터필수타입설명
id필수int경로 파라미터. 분석 ID. 본문 파라미터는 없습니다.
요청 예시
curl -X POST https://ops-388a48cadf.rankfree.co.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_noint그룹 번호(1부터 다시 매김)
short_links[].urlstring변경되지 않습니다 — 기존 주소 유지
short_links[].keywordsarray새로 배정된 키워드
short_links[].hit_countint누적 호출 횟수(초기화되지 않음)
messagestring실패(422) 시 사유 — 링크 없음 · 노출 키워드 없음 · 링크 수가 노출 키워드 수보다 많음
fieldstring실패(422) 시 short_links
API 키가 필요하신가요?

콘솔에서 직접 발급하고 권한·기간·한도·IP를 설정할 수 있습니다.

API 키 발급