서비스 계정으로 구글 플레이 개발자 API를 호출해 인앱 상품을 만들거나 고칠 때, 다른 API는 다 되는데 상품 쪽만 403 The caller does not have permission 이 나는 원인을 알아보는 글입니다.
앱에 광고 제거 상품을 하나 추가하려고 했습니다. 결제 확인용으로 만들어 둔 서비스 계정이 이미 있었고, 이 계정으로 빌드 업로드와 구매 확인이 잘 되고 있었으니 상품 등록도 API로 하면 되겠다고 생각했습니다. 그런데 새 수익 창출 API인 oneTimeProducts:batchUpdate 를 부르자 바로 403이 돌아왔습니다.
- 같은 계정으로 읽기와 빌드 업로드는 되고 상품 쓰기만 403이 났다
- 하루를 기다려도, 콘솔에서 상품을 손봐도 그대로였다
- 원인은 반영 지연이 아니라 권한을 준 위치였다. 앱 단위로 준 권한만으로는 부족했고 계정 단위 권한을 주자 바로 풀렸다
1. 증상: 읽기는 되는데 상품 만들기만 403
서비스 계정에는 플레이 콘솔의 사용자 및 권한 화면에서 해당 앱 권한을 넉넉히 줘 둔 상태였습니다. 상품 목록 조회, 구매 토큰 확인, 테스트 트랙 업로드까지 전부 200이 나왔는데, 상품을 만드는 요청만 막혔습니다.

에러 메시지는 PERMISSION_DENIED 하나뿐이라 무엇이 부족한지는 알려 주지 않습니다. 콘솔에서 그 앱의 권한을 열어 보면 앱 정보 관리 항목 설명에 "가격을 수정하고 인앱 상품을 관리"라고 적혀 있고 체크도 돼 있어서, 처음엔 권한 문제가 아니라고 판단했습니다.
2. 먼저 걸러야 할 함정 두 가지
권한을 의심하기 전에 요청 자체가 맞는지부터 보는 게 좋습니다. 이 API는 경로 대소문자가 섞여 있어서, 공식 명세(discovery 문서)를 보면 get, list, batchUpdate 는 oneTimeProducts 인데 patch 하나만 onetimeproducts 로 소문자입니다.
patch 를 낙타 표기로 보내면 JSON이 아니라 구글의 HTML 404 페이지가 오고, 반대로 get 을 소문자로 보내도 404가 납니다. 404가 나면 경로부터 의심하고, 403이 JSON으로 왔다면 경로는 맞게 들어간 것이라 권한 쪽을 보면 됩니다.
예전 API인 inappproducts 를 부르면 같은 403이라도 메시지가 Please migrate to the new publishing API. 로 다릅니다. 이건 권한이 아니라 옛 API가 막힌 것이니, 새 oneTimeProducts 로 옮기면 됩니다.
3. 헛짚은 두 가지
처음엔 권한 반영 지연이라고 봤습니다. 플레이 콘솔은 권한 변경이 API에 반영되기까지 하루쯤 걸릴 수 있다는 이야기가 많아서 기다렸는데, 18시간이 지나도 그대로였습니다.
다음으로 권한보다 상품을 먼저 만든 탓을 의심했습니다. 서비스 계정에 권한을 주기 전에 콘솔에서 만든 상품이 있으면 API가 거부되고, 콘솔에서 상품을 아무거나 한 번 수정해 저장하면 풀린다는 우회책이 알려져 있습니다. 실제로 이 계정도 상품을 만든 뒤에 권한을 줬기에 그대로 해 봤지만, 저장 직후와 몇 분 뒤에 다시 불러도 403이었습니다.
둘 다 그럴듯했지만 확인하지 않은 추정이었고, 시간만 썼습니다. 그래서 추정을 그만두고 막힌 범위부터 재 보기로 했습니다.
4. 어디까지 막혔는지 가르기
새로 만들면 되돌리기 번거로우니, 내용은 그대로 두고 저장만 하는 요청으로 비교했습니다. 스토어 등록정보는 받은 값을 그대로 다시 넣고 edit을 커밋하지 않은 채 지웠고, 상품은 기존 상품 설명을 그대로 다시 저장해 봤습니다.

결과가 깔끔하게 갈렸는데, 스토어 등록정보 쓰기는 200인데 상품 쓰기는 403이었습니다. 여기에 가격 변환 API(pricing:convertRegionPrices)를 하나 더 넣어 봤는데, 이건 원화 가격을 넣으면 나라별 가격을 계산만 해 주고 아무것도 바꾸지 않는 API인데도 403이 났습니다.
그러니 "쓰기 권한이 없다"가 아니라 상품과 가격, 곧 수익 창출 쪽 API 전체가 막혀 있다는 것으로 좁혀졌고, 이렇게 무해한 요청으로 가르면 운영 중인 상품을 건드리지 않고도 범위를 확인할 수 있습니다.
5. 관리자로 한 번 가르고, 계정 권한으로 좁히기
권한 문제인지 아닌지를 확정하려고, 서비스 계정의 계정 권한 탭에서 "관리자(모든 권한)"를 잠깐 켜고 같은 요청을 다시 보냈습니다. 상품 수정과 가격 변환이 바로 200으로 바뀌었고, 기다릴 필요도 없었습니다.
권한 문제라는 게 확정됐으니 관리자는 끄고, 필요한 것만 계정 권한에 넣었습니다. 앱 권한에는 이미 있던 항목들인데, 그걸 계정 단위로도 준 것입니다.
- 앱 정보 보기 및 보고서 일괄 다운로드(읽기 전용)
- 재무 데이터, 주문, 취소 설문조사 응답 보기
- 주문 및 구독 관리
- 앱 정보 관리(이 항목은 원래 계정 권한에도 있었음)
이 상태에서도 상품 수정과 가격 변환이 모두 200이었습니다. 정리하면, 이 계정에서는 같은 이름의 권한을 앱 단위로 준 것만으로는 수익 창출 API가 통과되지 않았고, 계정 단위로 주자 즉시 통과됐습니다. 새로 넣은 세 항목을 한꺼번에 추가해서 그중 정확히 어느 것이 결정적이었는지는 하나씩 빼 보지 않았습니다.
관리자 권한은 확인용으로만 쓰고 바로 끄는 게 좋습니다. 서비스 계정 키는 보통 서버에 놓이는데, 관리자 권한이 붙어 있으면 키가 새는 순간 사용자 초대나 삭제까지 가능해지기 때문입니다.
6. 지금 붙어 있는 권한을 API로 확인하기
콘솔 화면은 앱 권한과 계정 권한이 탭으로 나뉘어 있어 한눈에 비교하기 어렵습니다. 사용자 목록 API를 부르면 서비스 계정에 실제로 붙은 계정 단위 권한이 그대로 나옵니다.

developers/{개발자 ID}/users 를 부를 때 pageSize 는 -1 만 받고, 다른 값을 넣으면 Pagination is not currently available. The page_size parameter must be set to -1. 이라는 400이 납니다. 결과에서 이름이 _GLOBAL 로 끝나는 항목이 계정 단위 권한이라, 수익 창출 API가 403이면 여기에 CAN_VIEW_FINANCIAL_DATA_GLOBAL 같은 항목이 있는지부터 보면 됩니다.
7. 정리
서비스 계정으로 oneTimeProducts 같은 수익 창출 API를 쓰는데 다른 API는 되고 상품만 The caller does not have permission 이 난다면, 앱 권한 화면에 체크가 돼 있어도 계정 권한 탭을 따로 확인해야 합니다. 이번에는 앱 단위 권한으로는 끝까지 403이었고, 계정 단위로 주자 기다림 없이 바로 풀렸습니다.
원인을 모를 때는 기다리거나 우회책을 시도하기보다, 내용을 바꾸지 않는 요청으로 막힌 범위를 먼저 가르고 관리자 권한으로 한 번 확정하는 편이 빨랐습니다. 플레이스토어에 앱을 처음 올리는 과정은 웹사이트를 플레이스토어 앱으로 감쌀 때 막히는 곳에 따로 적어 두었습니다.
여기까지 구글 플레이 API로 인앱 상품을 만들 때 나는 403에 대해서 작성해봤습니다. 여기까지 읽어주셔서 감사합니다!
'일상' 카테고리의 다른 글
| 구글 클라우드(Google Cloud) 300달러 무료 크레딧이 Gemini API 비용에 안 빠질 때 (AI Studio 키와 Vertex AI 키 차이) (0) | 2026.10.18 |
|---|---|
| 안드로이드에서 배경음 반복 재생 때 이음매가 뚝 끊길 때 (MediaPlayer 반복 무음과 ExoPlayer 한 곡 반복) (0) | 2026.10.18 |
| 구글 플레이 조직 계정과 D-U-N-S 번호 신청 (영문 상호와 주소 표기) (0) | 2026.10.05 |
| 운영 인수인계용 시스템 인벤토리 표 만들기 (루트, DBA 계정은 빼는 이유) (0) | 2026.10.04 |
| AWS AgentCore가 대신해 주는 것과 안 해 주는 것 (LLM이 아닌 에이전트 운영 틀) (0) | 2026.10.04 |