Google Ads API 액세스 수준이 Test뿐일 때: Ads 스크립트로 내 계정 숫자 매일 받기

7분 분량

핵심 요약

  • 2026년 9월 9일부터 Google Ads API를 어디까지 쓸 수 있는지는 개발자 토큰이 아니라 Google Cloud 프로젝트의 액세스 수준이 정해요.
  • Test 액세스만 있는 프로젝트로 실제 광고 계정을 조회하면 CLOUD_PROJECT_NOT_APPROVED_FOR_PRODUCTION 오류가 나요.
  • 제 프로젝트는 Explorer 액세스 신청이 바로 거절됐어요. Basic 액세스는 Explorer 액세스가 있어야 신청할 수 있어요.
  • 내 계정 숫자를 읽기만 한다면 Google Ads 스크립트가 맞는 도구예요. 스크립트가 매일 시트에 숫자를 쓰고, 서비스 계정이 그 시트를 읽어요.

Google Ads API로 내 광고 계정을 조회했는데 CLOUD_PROJECT_NOT_APPROVED_FOR_PRODUCTION 오류가 난다면, Cloud 프로젝트의 API 액세스가 아직 Test 수준이라서예요. 비용, 클릭, 전환 수처럼 내 계정 숫자만 읽어 오면 되는 거라면 굳이 API를 쓰지 않아도 돼요. Google Ads 스크립트로 매일 Google 시트에 숫자를 쌓고 그 시트를 서비스 계정으로 읽어 오면 돼요.

Test 액세스만 있는 Cloud 프로젝트로 부르는 Google Ads API는 테스트 계정만 조회하고, 광고 계정 안의 스크립트는 Google 시트에 매일 숫자를 써서 서비스 계정이 그 시트를 읽는 흐름도

Test 액세스와 실제 계정

Google Ads API는 2026년 9월 9일에 개발자 토큰을 없앴어요. 예전에는 API 요청에 함께 보내는 개발자 토큰마다 액세스 수준이 있었어요. 그 뒤로는 Google Cloud 프로젝트마다 액세스 수준이 정해지고, 이 액세스 수준이 API를 어디까지 쓸 수 있는지 결정해요. 액세스 수준은 Test, Explorer, Basic, Standard 네 가지예요.

액세스 수준실제 계정 조회하루 API 작업 한도
Test안 됨테스트 계정 15,000
Explorer됨실제 계정 2,880
Basic됨15,000
Standard됨제한 없음

Test 액세스만 있는 프로젝트는 테스트 계정에만 요청할 수 있어요. 실제로 광고가 나가는 계정을 조회하면 API v25부터는 이런 오류가 나요. v24 이하에서는 같은 상황이 ACTION_NOT_PERMITTED로 나와요.

AuthorizationError.CLOUD_PROJECT_NOT_APPROVED_FOR_PRODUCTION

Google은 두 가지 해결법을 안내해요. 테스트 중이라면 요청이 테스트 계정을 향하는지 확인해요. 실제 계정이 필요하면 Cloud 콘솔의 Google Ads API 개요 페이지에서 액세스 수준을 Explorer 이상으로 올려요.

Explorer 신청이 거절되면

Explorer 액세스는 신청해야 받을 수 있어요. Google 문서는 대부분 자동 심사로 승인된다고 하지만 꼭 올려 준다고 하지는 않아요. 저는 2026년 11월 2일에 신청했는데 바로 거절됐어요. 화면에 나온 이유는 자격 기준을 충족하지 않는다는 한 줄(does not meet the eligibility criteria)뿐이었어요. 액세스 수준 문서에는 거절 기준도, 다시 신청하는 규칙도 나와 있지 않아요.

Basic 액세스는 현재 액세스 수준이 Explorer여야 신청할 수 있어서, Explorer가 거절되면 Basic으로 바로 넘어갈 수도 없어요. Basic을 신청하려면 Cloud 프로젝트의 브랜드 인증도 마쳐야 해요.

숫자를 사람이 눈으로 보기만 한다면 API도 스크립트도 필요 없어요. Google의 공식 Google Ads 앱(Android, iOS)에서 주요 지표와 캠페인 상태를 보고, 예산 부족이나 광고 비승인 같은 변화를 알림으로 받을 수 있어요.

제가 원한 건 제 프로그램이 광고 계정 하나의 숫자를 매일 받아 가는 것이었고, 캠페인을 API로 바꿀 일은 없었어요. 그래서 이 일은 API 대신 Google Ads 스크립트로 하기로 했어요.

Ads 스크립트에 필요한 것

Google Ads 스크립트는 광고 계정 안에서 Google 서버가 실행해요. Google 문서는 Google Ads 계정만 있으면 스크립트를 쓸 수 있고 개발자 인증 정보를 관리할 필요가 없다고 해요. 액세스 수준은 Cloud 프로젝트로 API를 부르는 프로그램에 붙어요. 스크립트는 계정 주인이 계정 안에서 쓰는 기능이라 Cloud 프로젝트가 필요 없어요. 제 계정에서도 스크립트가 실제 계정의 숫자를 문제없이 읽어 왔어요. 스크립트 실행에 추가 비용도 없어요.

스크립트 안의 AdsApp.search()는 Google Ads API와 같은 쿼리 언어인 GAQL로 보고서를 받아요. 실행은 매시간, 매일, 매주, 매월 중에서 예약해요. 보고서를 시트에 그대로 옮기기만 하면 된다면 AdsApp.report()의 exportToSheet()도 있어요. 저는 비용을 통화 단위로 바꾸고 열 이름을 정하려고 AdsApp.search()로 행을 받아서 썼어요.

스크립트로 매일 시트에 쓰기

아래는 제가 쓰는 스크립트에서 캠페인별 하루 숫자를 시트에 쓰는 부분이에요. 열은 몇 개만 남겼고, 시트 주소와 서비스 계정 이메일은 플레이스홀더로 바꿨어요.

var SHEET_URL = '';  // 첫 실행 뒤 로그에 찍힌 시트 주소를 넣어요
var READER = '[email protected]';
var START_DATE = '2026-11-01';

function main() {
  var ss = SHEET_URL ? SpreadsheetApp.openByUrl(SHEET_URL) : SpreadsheetApp.create('ads-daily');
  Logger.log('Sheet: ' + ss.getUrl());
  var viewers = ss.getViewers().map(function (u) { return u.getEmail(); });
  if (viewers.indexOf(READER) < 0) ss.addViewer(READER);

  var tz = AdsApp.currentAccount().getTimeZone();
  var today = Utilities.formatDate(new Date(), tz, 'yyyy-MM-dd');
  var query =
    'SELECT segments.date, campaign.name, metrics.impressions, metrics.clicks, ' +
    'metrics.cost_micros, metrics.conversions FROM campaign ' +
    "WHERE segments.date BETWEEN '" + START_DATE + "' AND '" + today + "' " +
    'ORDER BY segments.date';

  var rows = [['date', 'campaign', 'impressions', 'clicks', 'cost', 'conversions']];
  var it = AdsApp.search(query);
  while (it.hasNext()) {
    var r = it.next();
    rows.push([r.segments.date, r.campaign.name, Number(r.metrics.impressions || 0),
               Number(r.metrics.clicks || 0), Number(r.metrics.costMicros || 0) / 1e6,
               Number(r.metrics.conversions || 0)]);
  }
  var daily = ss.getSheetByName('daily') || ss.insertSheet('daily');
  daily.clearContents();
  daily.getRange(1, 1, rows.length, rows[0].length).setValues(rows);
}

스크립트는 실행할 때마다 시작일부터 오늘까지를 통째로 다시 써요. 그래서 하루를 건너뛰거나 두 번 돌아도 시트가 꼬이지 않아요. GAQL은 필드 이름을 cost_micros처럼 밑줄로 쓰지만, 결과 객체에서는 costMicros처럼 카멜케이스로 읽어요. 비용은 micros 단위라서 1,000,000으로 나눠야 계정 통화 금액이 돼요. 제 계정처럼 앱 설치를 전환으로 잡는 캠페인에서는 conversions가 설치 수예요.

제 시트에 처음 실제 숫자가 들어온 건 2026년 11월 3일 오전 6시 21분(한국 시간) 실행이었어요. 그 행에서 위 코드의 열만 남기고 캠페인 이름을 줄이면 이래요.

date        campaign            impressions  clicks  cost        conversions
2026-11-03  us-android-install  47           5       856.496924  2

비용이 원 단위 아래까지 나오는 건 micros 값을 1,000,000으로 나눴기 때문이에요. 이 행은 11월 3일 새벽 6시간 안팎만 담고 있어요. 다음 실행이 같은 날짜 행을 다시 쓰면서 숫자가 채워져요.

스크립트를 넣는 곳은 Google Ads 화면의 도구 → 일괄 작업 → 스크립트예요. 처음 실행할 때 승인(Authorize)을 요청해요. 승인한 뒤 한 번 실행하면 시트가 생기고 로그에 시트 주소가 찍혀요. 그 주소를 SHEET_URL에 넣은 다음에 실행 빈도를 매일로 정하세요. 주소가 비어 있으면 실행할 때마다 새 시트가 생겨요.

시트는 스크립트가 만들게 하세요

처음에 저는 시트를 손으로 만들고 그 주소를 스크립트에 넣었는데, 스크립트가 No item with the given ID could be found 오류로 멈췄어요. Google 문서에서 이 오류의 원인은 찾지 못했어요. 그래서 SHEET_URL을 비워 두고 스크립트가 SpreadsheetApp.create()로 시트를 만들게 했고, 그 시트는 문제없이 열렸어요. 로그에 찍힌 주소를 SHEET_URL에 넣으면 다음 실행부터 같은 시트에 써요.

같은 오류가 나면 시트를 스크립트가 직접 만들게 해 보세요. 시트를 서비스 계정에 공유하는 일도 스크립트의 addViewer()가 해요.

서비스 계정으로 시트 읽기

시트를 읽는 쪽은 Google Ads API가 아니라 Google Sheets API를 써요. Cloud 프로젝트에서 Sheets API를 켜고 서비스 계정1 키로 읽기 전용 범위만 요청하면 돼요. SHEET_ID는 시트 주소에서 /d/와 /edit 사이에 있는 값이에요. Python으로는 이렇게 읽어요.

from google.auth.transport.requests import AuthorizedSession
from google.oauth2 import service_account

SHEET_ID = "<시트 ID>"
creds = service_account.Credentials.from_service_account_file(
    "key.json", scopes=["https://www.googleapis.com/auth/spreadsheets.readonly"]
)
s = AuthorizedSession(creds)
url = f"https://sheets.googleapis.com/v4/spreadsheets/{SHEET_ID}/values/daily!A:F"
rows = s.get(url).json().get("values", [])

서비스 계정은 시트의 보기 권한만 있어서 시트를 고칠 수 없어요. 저는 필요할 때 꺼내 보는 운영 요약에 이 시트를 연결해 두었어요.

숫자를 읽을 때 조심할 점

Google Ads는 전환을 광고를 클릭한 날짜에 기록해요. 어제 클릭한 사람이 오늘 앱을 설치하면 어제 행의 숫자가 나중에 늘어요. 매일 시작일부터 다시 쓰는 스크립트는 이렇게 늘어난 숫자도 반영해요. 다만 최근 날짜일수록 숫자가 나중에 더 늘 수 있다고 보고 읽으세요. 전환이 일어난 날짜로 세고 싶으면 쿼리에 metrics.conversions_by_conversion_date를 쓰세요.

스크립트는 Google Ads 안에서 예약한 때나 손으로 실행할 때만 돌아요. 내 서비스가 사용자 요청이 올 때마다 광고 계정을 바로 조회해야 한다면 스크립트로는 할 수 없으니, Explorer 액세스부터 신청해 두세요.

자주 묻는 질문

미리보기로 실행해도 시트가 생기나요? 네. Google 문서에 따르면 미리보기는 AdsApp으로 하는 작업만 실제로 하지 않고, 시트 같은 다른 서비스는 그대로 바뀌어요. 그래서 SHEET_URL이 비어 있으면 미리보기를 돌릴 때마다 새 시트가 생겨요.

스크립트가 캠페인을 바꿀 수도 있나요? 네, Ads 스크립트는 캠페인을 조회할 수도 있고 바꿀 수도 있어요. 위 스크립트는 AdsApp.search()로 읽기만 하고 캠페인을 바꾸는 코드가 없어요.

출처

Google 문서의 날짜는 문서에 적힌 그대로 옮겼어요. 문서에 시간대 표기는 없어요.

각주

  1. 서비스 계정은 사람이 아니라 프로그램이 Google API를 부를 때 쓰는 계정이에요. Cloud 콘솔에서 만들고 JSON 키 파일로 인증해요.

공유하기