네이버 지도 Static Map API 401 에러 — 결제 문제가 아니라 도메인이 레거시였다
블로그 글에 넣을 실제 위치 지도를 자동 생성하는 스크립트를 만들면서, 네이버 클라우드 플랫폼(NCP)의 Static Map API를 붙였다. 결제 수단까지 이미 등록돼 있었는데도 401 에러가 계속 떴고, 원인은 전혀 다른 곳에 있었다.
증상 — 401 Permission Denied
Static Map API에 좌표를 넘겨 이미지를 요청하면 다음 에러가 돌아왔다.
$ curl -s "https://naveropenapi.apigw.ntruss.com/map-static/v2/raster?w=768&h=768¢er=126.9780,37.5665&level=16&format=png" \
-H "x-ncp-apigw-api-key-id: ${CLIENT_ID}" \
-H "x-ncp-apigw-api-key: ${CLIENT_SECRET}"
401 Permission Denied — A subscription to the API is required (errorCode 210)
errorCode 210과 “subscription required” 문구를 보고 가장 먼저 의심한 건 NCP 계정에 결제 수단이 등록돼 있지 않다는 것이었다. Static Map처럼 유료 전환 가능성이 있는 API는 결제 수단 미등록 상태에서 이런 메시지를 내는 경우가 흔하기 때문이다.
1차 가설 — 결제 수단 미등록은 아니었다
콘솔에서 결제 수단 등록 상태를 직접 확인해보니 카드가 이미 정상 등록돼 있었다. 이 시점에서 흔한 원인 하나가 탈락했다 — 결제 문제였다면 애초에 카드를 새로 등록해야 했겠지만, 그럴 필요가 없었다. 그런데도 같은 요청은 여전히 401을 반환했다.
진짜 원인 — 콘솔 개편과 도메인 분리
Application 등록 자체는 정상이었다(“Application Services > Maps” 카테고리에서 Static Map 권한을 포함해 새로 만들었다). 문제는 요청을 보낸 도메인이었다. 처음 참고한 문서(ai-naver-mapsstaticmap-raster, “AI·NAVER API” 카테고리)는 다음 도메인을 예시로 쓰고 있었다.
https://naveropenapi.apigw.ntruss.com/map-static/v2/raster
이 도메인은 예전 콘솔 체계에서 발급한 키를 기준으로 한 것이었다. 반면 지금 콘솔의 “Application Services > Maps”에서 새로 발급한 키는 이 도메인에 구독돼 있지 않고, 대신 별도 도메인에 물려 있었다.
https://maps.apigw.ntruss.com/map-static/v2/raster
경로(/map-static/v2/raster), 요청 파라미터(w, h, center, level, format), 인증 헤더(x-ncp-apigw-api-key-id, x-ncp-apigw-api-key)는 완전히 동일하다. 차이는 오직 호스트명 하나뿐이었다.
async function fetchStaticMap() {
const url =
`https://maps.apigw.ntruss.com/map-static/v2/raster` +
`?w=${W}&h=${H}¢er=${lon},${lat}&level=${level}&format=png&lang=ko`;
const res = await fetch(url, {
headers: {
"x-ncp-apigw-api-key-id": CLIENT_ID,
"x-ncp-apigw-api-key": CLIENT_SECRET,
},
});
if (!res.ok) {
throw new Error(`Static Map 요청 실패 ${res.status}: ${(await res.text()).slice(0, 300)}`);
}
return Buffer.from(await res.arrayBuffer());
}
같은 키, 같은 파라미터로 도메인만 바꿔 실제로 확인해보니 즉시 200과 함께 지도 PNG가 돌아왔다. 서울시청 좌표로 테스트한 결과 한글 도로명·지하철역·건물명까지 정상적으로 렌더링됐다 — 결제나 권한 문제가 아니라 순전히 도메인 미스매치였다는 뜻이다.
왜 도메인만 바꿔서 원인을 확정할 수 있었나
만약 결제 수단이나 Application 권한 자체가 문제였다면, 도메인을 바꿔도 똑같이 401이 떴어야 한다. 도메인만 바꿨는데 같은 키가 통과했다는 건 인증 정보 자체는 처음부터 유효했고, 그 인증 정보가 구독된 대상(도메인) 이 요청 URL과 일치하지 않았을 뿐이라는 걸 의미한다. 이런 식으로 변수를 하나씩 고정하고 나머지 하나만 바꿔가며 결과를 대조하면, 로그 메시지의 표면적 문구(“subscription required”)가 가리키는 원인과 실제 원인이 다를 때도 빠르게 구분할 수 있다.
정리
NCP Static Map API의 401 에러는 결제 수단 문제가 아니라 도메인 문제였다. 콘솔이 “Application Services > Maps” 체계로 개편되면서 새로 발급되는 키는 새 도메인(maps.apigw.ntruss.com)에만 구독되는데, 검색되는 예제 코드와 일부 공식 문서 카테고리는 여전히 레거시 도메인(naveropenapi.apigw.ntruss.com)을 쓰고 있었다. 같은 API 키로 401을 만나면 결제·권한 확인과 함께 요청 도메인이 최신 문서 기준인지도 같이 의심해볼 만하다.
자주 묻는 질문
401 Permission Denied가 뜨면 뭘 먼저 의심해야 하나요?
결제 수단 미등록이 흔한 원인이라 먼저 의심했지만, 이 경우처럼 결제 수단이 이미 등록돼 있어도 도메인이 다르면 같은 에러가 뜹니다. 콘솔에서 결제 수단과 Application 구독 상태를 먼저 확인하고, 둘 다 정상인데도 401이면 요청 URL의 도메인 자체를 의심해야 합니다.
왜 레거시 도메인을 쓰게 됐나요?
예전 'AI·NAVER API' 카테고리 문서(ai-naver-mapsstaticmap-raster)가 naveropenapi.apigw.ntruss.com 도메인을 예시로 쓰고 있었고, 검색되는 코드 예제·커뮤니티 글도 대부분 이 도메인을 그대로 인용하고 있었습니다. 콘솔이 'Application Services > Maps'로 개편되면서 새로 발급되는 키는 새 도메인에만 구독되는데, 문서와 예제 코드가 그 변화를 따라가지 못한 상태였습니다.
두 도메인이 완전히 다른 서비스인가요?
경로(map-static/v2/raster)와 요청 파라미터, 인증 헤더(x-ncp-apigw-api-key-id / x-ncp-apigw-api-key)는 동일합니다. 차이는 오직 호스트명뿐입니다. 다만 콘솔에서 발급한 키의 구독 대상이 도메인별로 분리돼 있어서, 새 콘솔로 발급한 키는 레거시 도메인 요청에 대해 401을 반환합니다.
어떻게 확정했나요?
같은 API 키와 같은 요청 파라미터를 도메인만 바꿔 다시 호출했습니다. 레거시 도메인은 401이 유지됐고 새 도메인은 즉시 200과 함께 실제 지도 PNG를 반환했습니다 — 키나 결제 문제였다면 도메인을 바꿔도 똑같이 실패했어야 하므로, 이 대조가 원인을 확정하는 근거였습니다.