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 URL | https://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 URL | https://ops-388a48cadf.rankfree.co.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://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}
]
}
]
}| 필드 | 타입 | 설명 |
|---|---|---|
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://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_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://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_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://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}
]
}
}| 필드 | 타입 | 설명 |
|---|---|---|
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://ops-388a48cadf.rankfree.co.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://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.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 | 업체 대표 태그(문자열 배열). 없으면 빈 배열 |
경쟁분석 scope: compete
순위추적 슬롯(키워드 x 플레이스)을 기준으로 네이버 플레이스 상위 노출 경쟁 업체를 수집해, 내 플레이스와 리뷰·평점·사진·정보충실성 등의 신호를 비교·점수화합니다.
분석 실행(POST)은 수집을 동기로 처리해 수십 초가 걸리며, 결과는 하루 1건의 일자별 스냅샷으로 저장되어 이후 조회(GET)로 언제든 다시 읽을 수 있습니다.
점수 N1/N2/N3·D1~D10 은 랭크프리가 관측 신호로 산출한 자체 추정치이며 네이버가 공개하는 공식 지표가 아닙니다.
| 항목 | 규칙 |
|---|---|
| 인증 | Authorization: Bearer rk_... 헤더(또는 X-API-KEY). compete 권한이 있는 API 키만 호출할 수 있습니다 |
| Base URL | https://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_rank 는 300 |
| 오류 | 401 키 무효·만료 / 403 scope 없음·타인 슬롯 / 404 존재하지 않는 slotId(경로의 슬롯은 라우트 모델 바인딩이라 소유 검사보다 먼저 404 가 납니다) / 429 일일 호출 한도 초과 또는 네이버 조회 차단 |
/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_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://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
}| 필드 | 타입 | 설명 |
|---|---|---|
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://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/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 |
키워드분석 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://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
}| 필드 | 타입 | 설명 |
|---|---|---|
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://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
}| 필드 | 타입 | 설명 |
|---|---|---|
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://ops-388a48cadf.rankfree.co.kr/keyword/{share_token} 로 로그인 없이 리포트를 열 수 있습니다 |
message | string|null | 상세 데이터가 없을 때 사유 문구. 정상 조회 시 null |
상세 지표 소스(검색광고 세션)에 연결할 수 없을 때 반환합니다. 데이터 없음(200 + detail: null)과 구분되는 일시 장애이므로, 잠시 후 같은 요청을 재시도하면 됩니다.
HTTP/1.1 503 Service Unavailable
{
"data": null,
"message": "상세 분석 소스에 일시적으로 연결할 수 없습니다. 잠시 후 다시 시도하세요."
}마케팅 상품 주문 scope: order
판매 중인 마케팅 상품을 조회하고, 외부 시스템에서 바로 주문을 접수하고, 주문 상태를 확인합니다.
검증·금액 계산은 웹 주문과 완전히 동일한 로직(OrderPlacer)을 공유하므로 화면 주문과 결과가 같습니다.
주문은 pending(접수) 상태로 생성되며, 주문에 쓰는 product_id는 GET /products 응답의 id입니다.
공통 주문 규칙 — 아래 규칙은 POST /orders 전체에 적용됩니다. 상품마다 다르므로 주문 전 GET /products/{id} 로 스펙을 먼저 확인하세요.
| 항목 | 규칙 |
|---|---|
quantity |
수량. 상품 상세의 fields 에 daily_qty 필드가 있으면 본문의 quantity 는 무시되고 fields.daily_qty 값이 수량이 됩니다. 그 필드가 없을 때만 quantity 를 사용하며, 미전달 시 0 으로 평가됩니다. min_quantity ~ max_quantity 범위를 벗어나면 422 |
days |
quantity_mode 가 daily 인 상품에만 의미가 있습니다. 상품에 start_date 와 end_date 필드가 둘 다 있을 때만 days 가 무시되고 종료일 − 시작일 + 1 로 계산됩니다. 둘 다 갖춰지지 않은 상품(두 필드가 모두 없거나 start_date 만 있는 경우 등)은 days 를 쓰며, 미전달 시 min_days 가 적용됩니다. quantity_mode 가 total 이면 일수는 1로 취급되고 응답 days 는 null |
fixed_quantity |
값이 있는 고정 수량(패키지) 상품은 quantity·fields.daily_qty 로 무엇을 보내든 고정값으로 접수됩니다. 저장되는 daily_qty 값도 고정값으로 덮어써집니다 |
fixed_days |
quantity_mode 가 daily 인 상품에서만 적용됩니다. 이때 값이 있는 고정 기간 상품은 days 를 보내도 고정 일수로 접수되고 min_days 검증도 건너뜁니다. start_date 필드가 있으면 시작일만 보내면 되고(누락 시 422 field: "days"), end_date 필드까지 있으면 종료일은 시작일 + 고정일수 − 1 로 서버가 재계산해 제출값을 덮어씁니다. quantity_mode 가 total 이면 fixed_days 는 무시되고 일수 1·응답 days 는 null |
| 날짜 필드 | 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). 모든 금액은 원 단위 정수 |
/products
주문 가능 상품 목록
판매 중(활성)인 마케팅 상품을 제목 오름차순으로 모두 반환합니다. 쿼리 파라미터는 없습니다. 여기서 얻은 id 를 POST /orders 의 product_id 로 사용하며, orderable 이 false 인 상품은 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[].id | int | 상품 번호 — 주문 시 product_id 로 사용(관리자 상품 목록의 번호와 동일) |
products[].title | string | 상품명 |
products[].type | string | 상품 유형 코드(REWARD·EXPERIENCE·SNS·BLOG_REVIEW·REVIEW 등) |
products[].type_name | string | 상품 유형 이름(한글) |
products[].unit_price | int | 단가(원) |
products[].quantity_mode | string | 과금 방식 — daily(단가×일수량×일수) 또는 total(단가×수량) |
products[].min_quantity | int | 최소 수량 |
products[].max_quantity | int | 최대 수량 |
products[].min_days | int | 최소 기간(일) |
products[].fixed_quantity | int|null | 값이 있으면 수량 고정(입력 무시) |
products[].fixed_days | int|null | 값이 있으면 기간 고정(종료일 자동 계산). quantity_mode: "daily" 상품에서만 적용 |
products[].earliest_start_date | string | 선택 가능한 가장 빠른 시작일(YYYY-MM-DD) |
products[].orderable | bool | false = 필수 파일 첨부 필드가 있어 API 주문 불가 |
products[].not_orderable_reason | string|null | 주문 불가 사유(파일 필드 라벨). 주문 가능하면 null |
/products/{id}
상품 상세 · 주문 필드 스펙
목록 응답의 모든 필드에 더해 description 과 주문 입력 필드 스펙(fields) 을 반환합니다. POST /orders 의 fields 객체는 여기 나온 key 를 그대로 키로 사용합니다. 다만 fields 에는 고객 입력 항목뿐 아니라 운영자 전용 숨김(내부) 필드도 같은 모양으로 섞여 내려오고, 응답만으로는 이를 구분할 수 없습니다(숨김 필드는 값을 보내도 무시되고 required 검증도 하지 않습니다 — 위 공통 주문 규칙 참고). 판매 중이 아니거나 없는 상품이면 404 입니다.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
id | 필수 | int | 경로 파라미터 — 상품 번호(GET /products 의 id) |
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.description | string|null | 상품 설명(상세 조회에만 포함) |
product.fields[].key | string | 주문 시 fields 객체에 쓰는 키(daily_qty·start_date·end_date 는 수량·기간 시스템 필드). 숨김(내부) 필드의 키는 보내도 무시됩니다 |
product.fields[].label | string | 필드 이름(오류 메시지에 그대로 등장) |
product.fields[].type | string | TEXT·TEXTAREA·URL·NUMBER·SELECT·MULTI_SELECT·TOGGLE·DATE·FILE·IMAGE·ADDRESS·MISSION_OPTIONS·TAGS |
product.fields[].required | bool | 필수 여부. 누락 시 422 f_{필드키}. 단 숨김(내부) 필드는 예외 — true 여도 검증하지 않고 보낸 값도 무시되며, 응답만으로는 숨김 여부를 알 수 없습니다(기본값이 true 라 숨김 + required: true 조합이 흔합니다) |
product.fields[].help | string|null | 입력 도움말 |
product.fields[].options | array|null | SELECT·MULTI_SELECT 의 {value, label} 목록. 그 외에는 null |
product.fields[].contains | string|null | 입력값에 반드시 포함돼야 하는 문자열(위반 시 422). 반대 규칙인 금지 문자열(not_contains)은 이 응답에 포함되지 않습니다 |
product.fields[].api_supported | bool | false = FILE·IMAGE 필드로 API 로는 값을 보낼 수 없음 |
/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"}}'{
"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_no | string | 주문번호 — GET /orders/{orderNo} 조회 키 |
order.status | string | pending·processing·completed·canceled. 생성 직후는 항상 pending |
order.status_label | string | 상태 한글 표기(접수·진행중·완료·취소) |
order.product.id | int|null | 주문 상품 번호 |
order.product.title | string|null | 주문 상품명 |
order.quantity | int | 서버가 확정한 수량(고정 수량 상품이면 고정값) |
order.days | int|null | 서버가 확정한 기간(일). quantity_mode: "total" 상품은 null(fixed_days 가 있어도 무시) |
order.unit_price | int | 주문 시점 단가(원) |
order.discount_amount | int | 쿠폰 할인액(원). 쿠폰 미사용이면 0 |
order.total_price | int | 최종 결제 금액(원) = 단가 × 수량 × 일수 − 할인액 |
order.fields | object | 실제 저장된 입력값(URL 정규화·고정값 덮어쓰기·종료일 재계산·숨김 필드 기본값이 반영된 값). 값이 없으면 빈 객체 |
order.created_at | string | 접수 일시(ISO 8601, KST) |
| 코드 | field | 상황 |
|---|---|---|
| 404 | product_id | 상품이 없거나 판매 중이 아님 |
| 422 | product_id | 필수 파일 첨부 필드가 있는 상품(API 주문 불가) |
| 422 | f_{필드키} | 필수 필드 누락, earliest_start_date 이전 날짜, contains 불일치, not_contains(금지 문자열) 포함 등 동적 필드 검증 실패 |
| 422 | quantity | 수량이 min_quantity ~ max_quantity 범위 밖(quantity 미전달로 0 인 경우 포함) |
| 422 | days | 시작일·종료일 누락, 종료일이 시작일보다 이전, 최소 기간 미달 |
| 422 | user_coupon_id | 사용 불가 쿠폰(만료·사용됨·중지), 상품 미적용, 최소 주문 금액 미달 |
{
"message": "'플레이스 URL' 항목을 입력하세요.",
"field": "f_place_url"
}/orders
내 주문 목록
API 키 소유 계정의 주문을 최신순으로 반환합니다. 상태 필터와 페이지네이션을 지원하며, 각 항목의 구조는 POST /orders 의 order 와 동일합니다.
| 파라미터 | 필수 | 타입 | 설명 |
|---|---|---|---|
status | 선택 | string | pending·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 /orders 의 order 와 동일(order_no·status·status_label·product·quantity·days·unit_price·discount_amount·total_price·fields·created_at) |
meta.page | int | 현재 페이지 번호 |
meta.per_page | int | 페이지당 건수(보정된 실제 값) |
meta.total | int | 필터 적용 후 전체 주문 수 |
meta.last_page | int | 마지막 페이지 번호 — page 가 이 값에 도달하면 순회 종료 |
/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.status | string | pending(접수) → processing(진행중) → completed(완료), 또는 canceled(취소) |
order.status_label | string | 상태 한글 표기 |
order.* | - | 나머지 필드는 POST /orders 응답과 동일 |
{
"message": "주문을 찾을 수 없습니다."
}쇼핑 유입키워드 scope: shop_keyword
핵심 키워드 하나와 내 상품 URL 로 롱테일 키워드 조합을 자동 생성하고, 각 조합으로 네이버 쇼핑을 검색해 내 상품이 상위 N위 안에 노출되는 키워드만 골라냅니다. 분석을 만들면 순위 확인이 자동으로 끝까지 진행되며, 찾아낸 노출 키워드는 그룹으로 나눠 Short URL 로 바로 발급할 수 있습니다(규칙은 관리자 화면과 동일).
보내는 값은 핵심 키워드와 상품 URL 두 개뿐입니다. 상품 제목 · 상점명 · 가격 같은 상품 정보는 랭크프리가 알아서 수집합니다 — 따로 넣을 값이 없습니다. 수집에는 요청자 계정으로 로그인된 랭크프리 확장 프로그램이 필요하며, 확장이 켜져 있으면 화면을 열어 둘 필요 없이 자동으로 처리됩니다.
수집된 상품 정보로 롱테일 키워드를 자동으로 만들어, 키워드마다 쇼핑을 검색해
내 상품이 상위 N위 안에 노출되는지 확인합니다. 이 확인도 확장 프로그램이 이어서 처리합니다.
생성 응답은 확인을 기다리지 않고 바로 돌아오므로, 진행률은 GET /shop-keywords/{id} 의 progress 로 폴링합니다.
노출로 판정된 키워드(exposed_keywords)를 원하는 그룹 수로 나눠
그룹별 단축 URL 을 발급합니다. 발급된 url 과 배정 키워드를 발주 · 배포 시스템이 그대로 가져다 씁니다.
| 항목 | 공통 규칙 |
|---|---|
| Base URL | https://ops-388a48cadf.rankfree.co.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://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 가 비어 있고 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://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 와 동일 |
page | int | 현재 페이지 |
per_page | int | 페이지당 개수 |
total | int | 전체 분석 수 |
/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.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://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_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://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_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://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_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를 설정할 수 있습니다.