← 블로그 전체
AdMob · TypeScript · Indie

AdMob 광고 매출, Reporting API로 직접 가져오기

Google AdMob 광고 매출을 Reporting API 로 직접 받는 법 — OAuth(admob.readonly), networkReport POST, NDJSON·micros 파싱, 통화 함정. App Store Connect 와 비교.


horog 는 앱 광고 매출도 시급 계산에 넣어야 했다. 그래서 AdMob 을 붙였는데, App Store Connect 와는 결이 꽤 달랐다. App Store 는 JWT(ES256) 서명 + gzip TSV 였는데, AdMob 은 Google OAuth 2.0 + NDJSON + micros 다. 이 글은 그 차이와 부딪힌 함정 정리다.

Google 에서 받을 것

  1. Google Cloud Console → APIs & Services 에서 AdMob API 활성화
  2. OAuth 2.0 Client (Web) 생성 → Client ID / Client Secret
  3. 스코프 admob.readonly — 리포트 *읽기* 만. 광고 단위 생성·결제 정보 접근은 안 됨
  4. OAuth 동의 화면에 본인 Google 계정을 테스트 사용자로 등록
Google Calendar 와 같은 Google 계정이어도 스코프가 다르면 동의를 다시 받아야 한다. horog 는 AdMob 용 OAuth Client 를 따로 둔다.

OAuth — JWT 가 아니다

App Store 는 키 1개로 매 요청 JWT 를 굽지만, AdMob 은 표준 Google OAuth authorization code 플로우다. 핵심은 access_type=offline + prompt=consent — 이게 있어야 refresh_token 을 준다. 없으면 access token 만 받고 1시간 뒤 끊긴다.

typescript
// AdMob 은 App Store 처럼 JWT 가 아니라 Google OAuth 2.0.
// admob.readonly 스코프 + access_type=offline 로 refresh_token 확보.
const url = new URL("https://accounts.google.com/o/oauth2/v2/auth");
url.searchParams.set("client_id", process.env.ADMOB_OAUTH_CLIENT_ID!);
url.searchParams.set("redirect_uri", redirectUri);
url.searchParams.set("response_type", "code");
url.searchParams.set(
  "scope",
  "https://www.googleapis.com/auth/admob.readonly openid email profile",
);
url.searchParams.set("access_type", "offline"); // refresh_token 받으려면 필수
url.searchParams.set("prompt", "consent");

// 콜백에서 code → token 교환 (POST oauth2.googleapis.com/token,
// grant_type=authorization_code). 만료되면 grant_type=refresh_token 으로 갱신.

리포트 호출 — POST + JSON

먼저 GET /v1/accounts 로 publisher 를 찾는다 (보통 사용자당 1개, pub-XXXXXXXXXXXX 형식). 그다음 networkReport:generate 를 부르는데 — 이게 GET 이 아니라 POST 에 JSON body 다.

typescript
// 함정 1: 리포트는 GET 이 아니라 POST + JSON body.
// publisherId 는 GET /v1/accounts 의 첫 row (보통 사용자당 1개).
const res = await fetch(
  `https://admob.googleapis.com/v1/accounts/${publisherId}/networkReport:generate`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      reportSpec: {
        dateRange: {
          startDate: { year: 2026, month: 6, day: 1 },
          endDate: { year: 2026, month: 6, day: 7 },
        },
        dimensions: ["DATE", "APP"],
        metrics: ["ESTIMATED_EARNINGS", "IMPRESSIONS", "CLICKS"],
        // 함정 2: 통화 안 맞추면 계정 통화(KRW 등)로 옴 → USD 고정
        localizationSettings: { currencyCode: "USD", languageCode: "en-US" },
      },
    }),
  },
);

파싱 — NDJSON + micros

응답이 단일 JSON 객체가 아니라 NDJSON 이다. 줄마다 객체 하나 — 첫 줄은 header, 가운데가 데이터 row, 마지막이 footer. 줄 단위로 파싱하고 row 키가 있는 것만 쓴다. 그리고 매출은 micros 라서 100만으로 나눠야 달러가 된다.

typescript
// 함정 3: 응답이 NDJSON — 줄마다 JSON 1개 (header → row들 → footer).
const text = await res.text();
for (const line of text.trim().split("\n")) {
  const obj = JSON.parse(line);
  if (!("row" in obj)) continue; // header / footer line 은 건너뜀

  const date = obj.row.dimensionValues.DATE.value;       // "20260607" (YYYYMMDD)
  const app = obj.row.dimensionValues.APP.displayLabel;  // 앱 이름

  // 함정 4: ESTIMATED_EARNINGS 는 micros 정수 (1 USD = 1,000,000)
  const usd =
    Number(obj.row.metricValues.ESTIMATED_EARNINGS.microsValue) / 1_000_000;
}

날짜는 20260607 같은 YYYYMMDD 문자열로 온다. 앱별로 보려면 APP dimension 의 displayLabel 을 쓴다.

실제로 부딪힌 함정

  1. JWT 아님 — Google OAuth 2.0. access_type=offline 없으면 refresh_token 이 안 온다
  2. 리포트는 POST networkReport:generate, JSON body 에 reportSpec
  3. NDJSON — 줄마다 JSON. header·footer row 를 데이터와 섞지 말 것
  4. microsESTIMATED_EARNINGS 는 1 USD = 1,000,000. 그냥 더하면 100만 배 매출이 된다
  5. 통화 — 계정 통화로 옴. localizationSettings.currencyCode 로 USD 고정하거나, 환산을 직접
  6. 추정치 — ESTIMATED_EARNINGS 는 *추정*. 월말 확정 정산과 몇 % 차이날 수 있다. 시급 추세엔 충분하지만 회계용은 아님
  7. 401 → refresh — access token 만료 시 한 번 갱신 후 재시도

App Store 와 비교

  • 인증: App Store = JWT(ES256, 20분) / AdMob = OAuth refresh_token
  • 리포트: App Store = GET → gzip TSV / AdMob = POST → NDJSON
  • 금액: App Store = 소수 문자열 / AdMob = micros 정수
  • 지연: App Store = D+1 / AdMob = 당일도 부분 집계(추정)
horog 는 App Store·Google Play·AdMob·Stripe 등 9개 매출·시간 통합으로 사이드 프로젝트별 진짜 시급을 자동 계산해주는 SaaS 입니다. 무료로 시작.
AdMob 광고 매출, Reporting API로 직접 가져오기 — horog · horog