비개발자를 위한 카카오모먼트 API: 권한 확인과 하루 보고서
카카오모먼트 API의 사용 권한과 비즈니스 토큰을 구분하고 가상 응답으로 보고서 구조를 익힙니다. 캠페인 하나의 하루 성과를 조회한 뒤 날짜, 지표 그룹과 누락값을 확인합니다.

목차
세 줄로 먼저 읽기
이번 방문에서 한 편은 바로 볼 수 있습니다.
카카오모먼트 광고 보고서를 자동으로 만들고 싶다면 캠페인 하나의 하루 비용과 클릭수를 가져와 광고 관리자와 맞추는 것부터 시작합니다. API는 프로그램으로 광고 정보와 성과를 요청하는 기능입니다.
앱을 만들었다고 곧바로 모든 광고계정을 조회할 수 있는 것은 아닙니다. 먼저 사용 권한을 확인하고, 연결이 준비되지 않았다면 아래 가상 보고서로 구조를 익힙니다. 이 글의 조회 코드는 광고 생성이나 예산 변경을 하지 않습니다.
먼저 확인할 권한은 두 가지입니다
| 확인할 것 | 뜻 | 확인 결과 |
|---|---|---|
| 앱의 카카오모먼트 API 사용 권한 | 이 앱이 모먼트 API를 이용할 수 있는지 | 추가 기능 신청의 승인 상태 |
| 비즈니스 토큰에 인가된 광고계정 | 이 인증값으로 어느 계정의 정보를 읽을 수 있는지 | 동의한 자산과 접근 권한 |
여기서 인가는 사용자가 특정 자산의 접근을 허용하는 절차입니다. 앱의 이용 승인이 있더라도 조회할 광고계정의 접근 권한은 별도로 허용받아야 합니다.
현재 공식 문서는 앱 생성, 비즈 앱 전환, 앱 소유자 본인인증을 마친 뒤 [앱] > [추가 기능 신청]에서 사용 권한을 신청하도록 안내합니다. 신청과 승인은 다릅니다. 앱의 운영 주체와 사용 목적이 운영 정책에 부합하는지 검토하므로 규모만으로 신청 가능 여부나 승인 결과를 단정하지 않습니다. 카카오모먼트 시작 절차, 추가 기능 권한과 승인 기준
비즈 앱은 비즈니스 정보를 등록한 앱입니다. 비즈 앱 전환을 마친 뒤에도 카카오모먼트 API의 별도 사용 권한이 필요합니다. 소유자가 앱의 현재 상태와 신청 화면에 필요한 자료를 확인합니다. 계정과 기능에 따라 필요한 확인이 달라지므로 ‘10분이면 완료’나 ‘무조건 며칠’로 정하지 않습니다. 비즈 앱과 추가 기능 설정
사용 권한이 아직 없으면 가상 응답 연습이나 관리자에서 받은 보고서의 열을 확인하는 단계까지 진행합니다. API가 없다고 광고 보고서 개념을 배우지 못하는 것은 아닙니다.
비즈니스 토큰은 무엇인가요?
비즈니스 토큰은 사용자가 동의한 광고계정 등의 자산에 접근할 수 있음을 증명하는 비밀 인증값입니다. 일반 카카오 로그인 토큰과 용도가 다릅니다. 모먼트 운영 권한에 해당하는 동의항목은 moment_management입니다. 이 항목은 조회만을 뜻하는 권한이 아니므로 코드의 실제 요청 동작도 확인합니다. 비즈니스 인증과 동의항목
처음 보는 용어는 역할별로 구분하면 됩니다.
| 용어 | 역할 |
|---|---|
| REST API 키 | 인증을 요청하는 앱 식별 |
| Client Secret | 토큰 발급 요청을 확인하는 비밀값 |
| Redirect URI | 사용자 동의 후 인가 코드를 받을 서비스 주소 |
| 비즈니스 인가 코드 | 토큰 발급에 사용하는 일회성 코드 |
| 비즈니스 토큰 | 이후 광고 API 요청에 사용하는 인증값 |
리다이렉트 URI를 등록하고 필요한 비즈니스 동의항목을 설정합니다. 현재 새 REST API 키는 클라이언트 시크릿 기능이 기본 활성화되므로 실제 설정에 맞춰 토큰 발급 요청을 구성합니다. 비즈니스 인증 설정
인증 흐름은 다음과 같습니다. 아래는 구현 구조이며 실행 명령이 아닙니다.
1. 사용자 동의 요청
GET https://kauth.kakao.com/oauth/business/authorize
scope=moment_management
resource_ids=moment:조회할_광고계정_ID
등록한 redirect_uri와 앱의 client_id 사용
요청별 state를 만들고 응답에서 같은 값인지 검증
2. 인가 코드를 비즈니스 토큰으로 교환
POST https://kauth.kakao.com/oauth/business/token
앱 설정에 맞는 client_secret 등 필수값 사용
3. 보고서 조회 요청에 인증 정보 포함
Authorization: Bearer 비즈니스_토큰
adAccountId: 조회할_광고계정_IDstate는 인증 요청과 돌아온 응답이 같은 과정에 속하는지 확인하는 값입니다. 인증 담당자는 공식 문서에 따라 필수값과 오류 처리까지 구현합니다. 토큰과 시크릿, 인가 코드는 AI 대화나 공개 저장소에 넣지 않습니다. 인증 API 명세
첫 연습: 가상 응답에서 읽을 위치를 찾습니다
캠페인 목록에는 이름과 ID가 있고, 보고서에는 성과가 있습니다. 아래 JSON은 항목 이름과 값을 정해진 형식으로 표현한 데이터입니다. 다음은 실제 계정과 무관한 가상 자료입니다.
{
"content": [{"id": 1234, "name": "가상 캠페인"}]
}{
"code": 200,
"message": "Success",
"data": [{
"start": "2026-10-01",
"end": "2026-10-01",
"dimensions": {"campaign_id": "1234"},
"metrics": {"imp": 1000, "click": 20, "ctr": 2.0, "cost": 10000.5}
}]
}목록의 content에서 캠페인 id와 name을 읽습니다. 보고서는 data의 각 행에서 dimensions에 있는 캠페인 ID와 metrics에 있는 지표를 읽습니다. dimensions는 어떤 대상이나 구분의 행인지, metrics는 측정한 값이 무엇인지 알려 줍니다. 숫자 ID와 문자열 ID를 연결할 때는 둘 다 문자열처럼 같은 형식으로 맞춥니다. 캠페인 목록 응답
먼저 캠페인 ID와 비용을 찾아봅니다. 답은 1234와 10000.5이며, 목록과 보고서의 ID를 연결하면 캠페인 이름이 ‘가상 캠페인’임을 알 수 있습니다.
가상 응답의 클릭률은 20 / 1000 × 100 = 2%입니다. 비용의 소수도 임의로 버리지 않습니다. 반면 metrics가 {}라면 ‘지표가 비어 있음’으로 구분합니다. 지표가 없다고 비용과 클릭수가 0으로 확인된 것은 아닙니다.
첫 조회의 날짜와 지표 그룹을 정합니다
metricsGroup은 요청할 지표 묶음입니다. 기본 성과는 BASIC, 도달 등 추가 지표는 ADDITION을 사용합니다. 메시지 캠페인은 메시지용 그룹을 사용하므로 같은 코드를 모든 유형에 적용하지 않습니다.
| 첫 조회 항목 | 이 예제의 선택 |
|---|---|
| 대상 | 기본 지표를 지원하는 캠페인 하나 |
| 기간 | 한국 시간 기준 이미 지난 하루 |
start, end | 같은 날짜를 yyyyMMdd 형식으로 입력 |
timeUnit | DAY, 날짜별 집계 |
level | CAMPAIGN, 캠페인 단위 |
metricsGroup | BASIC |
dimension | 첫 조회에서는 생략 |
직접 지정한 start, end에는 오늘을 넣지 않습니다. 조회 범위는 총 31일 이내이며, 오늘 자료는 별도의 datePreset=TODAY 조건을 사용합니다. 공식 안내상 전일 보고서는 다음 날 오전 8시 이전까지 변동 가능한 지표로 봅니다. 정기 보고서는 수집 시각도 함께 기록해 갱신 전후의 차이를 구분합니다. 보고서 조회 조건
Python으로 캠페인 하나의 하루를 조회합니다
Python 3 표준 라이브러리로 실행하는 예시입니다. 아래 코드를 kakao_one_day.py로 저장합니다. 실행 환경에는 다음 환경 변수가 필요합니다. 환경 변수는 코드 밖에서 프로그램이 읽는 설정값이며 실제 토큰은 회사의 비밀값 관리 방식으로 설정합니다.
KAKAO_MOMENT_TOKEN
KAKAO_MOMENT_AD_ACCOUNT_ID
KAKAO_MOMENT_CAMPAIGN_ID
KAKAO_MOMENT_DATE (예: 20261001)import os, json, csv
from datetime import datetime, timezone, timedelta
from decimal import Decimal
from pathlib import Path
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from urllib.error import HTTPError
BASE = "https://apis.moment.kakao.com/openapi/v4"
TOKEN = os.environ["KAKAO_MOMENT_TOKEN"]
ACCOUNT = os.environ["KAKAO_MOMENT_AD_ACCOUNT_ID"]
CID = os.environ["KAKAO_MOMENT_CAMPAIGN_ID"]
raw_day = os.environ["KAKAO_MOMENT_DATE"]
if len(raw_day) != 8 or not raw_day.isascii() or not raw_day.isdigit():
raise ValueError("날짜는 YYYYMMDD 형식의 숫자 8개로 입력합니다.")
day = datetime.strptime(raw_day, "%Y%m%d").date()
if day >= datetime.now(timezone(timedelta(hours=9))).date():
raise ValueError("한국 시간 기준 이미 지난 날짜를 입력합니다.")
OUT = Path("kakao_one_day.csv")
if OUT.exists():
raise FileExistsError("기존 CSV를 보존하고 새 파일 이름이나 폴더를 사용합니다.")
def get_json(path, params=None):
url = BASE + path + ("?" + urlencode(params) if params else "")
request = Request(url, headers={
"Authorization": f"Bearer {TOKEN}", "adAccountId": ACCOUNT,
}, method="GET")
try:
with urlopen(request, timeout=30) as response:
return json.load(response, parse_float=Decimal)
except HTTPError as error:
raise RuntimeError(f"조회 실패: HTTP {error.code}") from None
listing = get_json("/campaigns")
if not isinstance(listing, dict) or not isinstance(listing.get("content"), list):
raise ValueError("캠페인 목록의 content 형식이 다릅니다.")
matches = [c for c in listing["content"]
if isinstance(c, dict) and str(c.get("id")) == CID]
if len(matches) != 1:
raise ValueError("이 계정에서 요청한 캠페인을 하나로 확인하지 못했습니다.")
report = get_json("/campaigns/report", {
"campaignId": CID, "start": raw_day, "end": raw_day,
"timeUnit": "DAY", "level": "CAMPAIGN", "metricsGroup": "BASIC",
})
if not isinstance(report, dict) or report.get("code") != 200:
raise ValueError("보고서 성공 응답이 아닙니다.")
if not isinstance(report.get("data"), list):
raise ValueError("보고서 data가 목록이 아닙니다.")
output = []
empty_rows = 0
for row in report["data"]:
if not isinstance(row, dict):
raise ValueError("보고서 행의 형식이 다릅니다.")
metrics = row.get("metrics")
if metrics == {} or row == {}:
empty_rows += 1
continue
dims = row.get("dimensions")
if not isinstance(metrics, dict) or not isinstance(dims, dict):
raise ValueError("지표와 조회 대상의 응답 형식이 다릅니다.")
if str(dims.get("campaign_id")) != CID:
raise ValueError("보고서 캠페인 ID가 요청과 다릅니다.")
if row.get("start") != day.isoformat() or row.get("end") != day.isoformat():
raise ValueError("보고서 날짜가 요청한 하루와 다릅니다.")
output.append([ACCOUNT, CID, matches[0].get("name"), day.isoformat(),
metrics.get("imp"), metrics.get("click"), metrics.get("cost")])
if output:
with OUT.open("x", newline="", encoding="utf-8-sig") as handle:
writer = csv.writer(handle)
writer.writerow(["광고계정ID", "캠페인ID", "캠페인명", "날짜", "노출수", "클릭수", "비용"])
writer.writerows(output)
print(f"{len(output)}행 저장: {OUT}; 빈 지표 행 {empty_rows}개")
else:
print(f"저장할 성과가 없습니다. 빈 지표 행 {empty_rows}개, 조회 조건 점검이 필요합니다.")python3 kakao_one_day.py로 실행합니다. 성과 행이 하나이고 빈 행이 없으면 1행 저장: kakao_one_day.csv; 빈 지표 행 0개처럼 표시됩니다. CSV는 표 형태의 텍스트 파일이며 계정, 캠페인과 날짜부터 확인합니다. 비용과 클릭수를 광고 관리자에서 같은 조건으로 대조합니다. 이 예제는 한 캠페인을 요청하며, 전체 계정 수집이나 자동 인증 갱신을 구현한 코드는 아닙니다.
누락된 필드는 CSV 빈 칸, 실제 반환된 0은 0으로 남습니다. Decimal은 비용 소수를 다룰 때 쓰는 자료형입니다. 결과가 다르다면 단위를 임의로 나누거나 빈 값을 0으로 채우기 전에 원래 조회 조건부터 확인합니다.
지표를 늘릴 때는 필드 이름을 확인합니다
| 목적 | 지표 그룹과 필드 예 |
|---|---|
| 기본 성과 | BASIC: imp, click, ctr, cost |
| 도달과 클릭당 비용 | ADDITION: reach, cost_per_click |
| 메시지 성과 | MESSAGE: msg_send, msg_open, msg_click |
| 구매수 | PIXEL_SDK_CONVERSION: conv_purchase_1d, conv_purchase_7d |
| 구매 금액 | 같은 전환 그룹의 conv_purchase_p_1d, conv_purchase_p_7d |
| 광고수익률 | 같은 전환 그룹의 conv_purchase_p_per_cost_1d, conv_purchase_p_per_cost_7d |
1d와 7d는 서로 다른 기여 기간의 지표입니다. 기여 기간은 광고 반응 후 어느 기간의 전환을 광고 성과에 포함하는지 정하는 기준입니다. 두 값을 합산해 구매수를 만들지 않습니다. 보고서에 어느 기간을 썼는지 적습니다. 지표 그룹과 정확한 필드 이름
광고수익률을 뜻하는 ROAS도 전환 지표 그룹에 포함돼 있습니다. 보고서에서 퍼센트로 쓸지 배수로 쓸지 먼저 정하고 응답의 단위와 관리자 표시를 대조합니다. cost_per_imp처럼 이름만으로 1회 기준인지 1,000회 기준인지 혼동하기 쉬운 값도 정의를 확인한 뒤 사용합니다.
메시지 캠페인에 BASIC을 지정해 값이 없을 때 이를 광고 성과 0으로 해석하지 않습니다. 운영 중인 메시지 상품에 따라 전용 메시지광고 보고서가 필요한지도 확인합니다. 메시지광고 보고서
캠페인이 많아지면 요청 간격도 관리합니다
카카오모먼트 v4 보고서의 요청 간격은 종류별로 다릅니다.
| 보고서 경로 | 문서상 요청 간격 |
|---|---|
/adAccounts/report | 5초에 한 번 |
/campaigns/report | 5초에 한 번 |
/adGroups/report | 1초에 한 번 |
/creatives/report | 5초에 한 번 |
이 표는 모든 API에 같은 제한이 적용된다는 뜻이 아닙니다. 캠페인 보고서는 광고계정 번호와 앱 ID 기준의 제한을 확인하고 한 번에 최대 5개 캠페인까지만 요청합니다. 다른 프로그램이 같은 계정과 앱을 쓰는 경우도 고려합니다. API별 제한 목록
429 등의 제한 오류가 나면 즉시 재귀 호출을 반복하지 않습니다. 첫 예제는 오류를 표시하고 종료합니다. 수동으로 다시 실행할 때도 직전 캠페인 보고서 요청 후 최소 5초가 지났는지 확인합니다. 자동 수집으로 확장할 때는 제한된 재시도 횟수와 대기 시간, 실패한 캠페인 기록을 추가합니다.
세분화 기준인 dimension은 하나를 선택합니다. 연령과 성별 조합은 AGE_BAND_GENDER 같은 지원값으로 요청하며, 원하는 모든 조합을 임의로 만들 수는 없습니다. 캠페인 유형별로 지원하는 기준도 확인합니다.
다른 매체와 합치기 전에 확인할 것
캠페인 이름이 같다는 이유로 다른 매체의 행을 같은 대상으로 묶지 않습니다. 매체명, 계정 ID와 캠페인 ID를 함께 남깁니다.
| 비교 항목 | 확인할 차이 |
|---|---|
| 비용 | 통화, 소수 처리, 세금 포함 여부와 금액 단위 |
| 클릭 | 각 매체에서 집계하는 클릭 행동 |
| 전환 | 구매 등 이벤트 종류, 추적 설정과 기여 기간 |
| 영상 | 재생 횟수인지 재생 비율인지 |
| ROAS | 퍼센트인지 배수인지, 매출과 비용의 기준 |
네 매체의 API 접근 난이도나 호출 속도를 일괄 순위로 정하지 않습니다. 소유 계정과 필요한 기능, 승인 상태에 따라 준비 과정이 다릅니다. 구글 Ads API, Meta Ads API, 네이버 검색광고 API도 각각 하루 조회가 맞은 뒤 통합합니다.
오류가 나면 확인할 순서
| 증상 | 확인할 곳 |
|---|---|
| 토큰 또는 권한 오류 | 앱 승인, 비즈니스 동의항목과 해당 광고계정 인가 |
| 캠페인이 없다고 나옴 | 광고계정 ID와 캠페인 ID, 목록의 content |
| 보고서가 비어 있음 | 날짜, 집행 여부와 캠페인에 맞는 지표 그룹 |
| 날짜 오류 | 8자리 형식과 한국 시간 기준 과거 날짜 |
KeyError | 해당 환경 변수 설정 여부 |
| 연결 실패 또는 시간 초과 | 네트워크와 서비스 상태 |
| CSV가 이미 있음 | 이전 결과를 보존하고 새 출력 이름 또는 폴더 사용 |
혼자 반복할 때는 날짜만 바꾸고 새 폴더에서 다시 실행합니다. 두 번째 조회도 광고 관리자와 일치하는지 확인한 다음 캠페인 수나 지표를 하나씩 늘립니다.
AI에게 구현을 맡길 때도 실제 키 대신 다음과 같이 범위를 전달합니다.
카카오모먼트의 읽기 전용 보고서 코드를 검토해 주세요.
권한과 비즈니스 토큰은 별도로 준비하며 비밀값은 제공하지 않겠습니다.
캠페인 목록은 content, 보고서 행은 data로 읽고 캠페인 ID를 대조합니다.
과거 하루와 캠페인 하나의 BASIC 지표만 조회합니다.
빈 metrics와 실제 0을 구분하고 기존 결과를 덮어쓰지 않습니다.
실제 호출 전 가상 응답으로 정상, 빈 결과와 오류를 검사해 주세요.3줄 요약
-
카카오모먼트 API는 사용 권한을 받은 앱과 해당 광고계정에 인가된 비즈니스 토큰으로 이용합니다.
-
처음에는 캠페인 하나의 과거 하루를 조회하고 광고 관리자와 날짜 및 지표를 대조합니다.
-
보고서 유형과 지표 그룹, 전환 기여 기간을 확인한 뒤 여러 캠페인이나 다른 매체로 수집 범위를 늘립니다.

제대로 이해했는지 한 문제로 확인해 볼까요?
답을 고르면 바로 풀이가 나와요.
캠페인 보고서의 metrics가 빈 객체라면 어떻게 기록해야 할까요?
이 글이 도움이 되었나요?
메일, 회의록, 리포트처럼 반복되는 일을 AI로 자동화하는 순서와 안전장치를 다룹니다
코스 전체 보기 →
새 글과 AI 소식을 메일로 받아 보세요
AI가 바꾸는 일과 도구, 측정 실무 이야기를 매주 한 번 보내 드려요.
