Google Ads API — Operational Setup Guide
The prompt search-volume feature (app/Services/Volumes/GoogleAdsClient.php) pulls
keyword volumes straight from Google Ads Keyword Planner via the
generateKeywordHistoricalMetrics REST endpoint. This replaced the paid DataForSEO
subscription — DataForSEO resold this exact data, so numbers and location codes are
identical. The API itself is free; you only need the credentials below.
Credentials you'll end up with
| Env variable | What it is | Where it comes from |
|---|---|---|
GOOGLE_ADS_DEVELOPER_TOKEN |
API access token for your Ads organization | Google Ads manager account → API Center |
GOOGLE_ADS_CLIENT_ID / GOOGLE_ADS_CLIENT_SECRET |
OAuth2 app identity | Google Cloud Console → Credentials |
GOOGLE_ADS_REFRESH_TOKEN |
Long-lived token for your Google login | One-time OAuth consent flow |
GOOGLE_ADS_CUSTOMER_ID |
The Ads account queried for metrics (10 digits, no dashes) | Google Ads UI, top-right |
GOOGLE_ADS_LOGIN_CUSTOMER_ID |
Manager (MCC) account ID — only if the account above sits under one | Manager account, top-right |
Step 1 — Create a Google Ads manager account (MCC) and get the developer token
Developer tokens are only issued to manager accounts, not regular Ads accounts.
- Create a manager account at https://ads.google.com/home/tools/manager-accounts/ (use the company Google account, e.g. the one behind [email protected]).
- If you have a regular Google Ads account already, link it under the manager account (Accounts → Sub-account settings → Link existing account).
- In the manager account: Admin (Tools & Settings) → API Center.
- Accept the terms → your developer token appears. Copy it.
- The token starts at Test Account access level — it can only query test accounts and returns dummy data. On the same API Center page, apply for Basic access (short form: describe the use case as "internal SEO tool fetching keyword search volumes via KeywordPlanIdeaService"). Approval typically takes 1–3 business days. Basic access allows 15,000 operations/day — far more than we need.
The token string itself never changes when access is upgraded; only its permission level does.
Step 2 — Google Cloud project + OAuth client
- Go to https://console.cloud.google.com/ → create a project (e.g.
answerix-ads-api). - APIs & Services → Library → search "Google Ads API" → Enable.
- APIs & Services → OAuth consent screen:
- User type: External → Create.
- Fill app name + support email; no scopes need to be added on the form itself.
- Add the Google account that owns the Ads manager account as a test user.
- APIs & Services → Credentials → Create Credentials → OAuth client ID:
- Application type: Desktop app.
- Copy the Client ID and Client Secret.
- Important: while the consent screen is in Testing status, refresh tokens
expire after 7 days. Once everything works, go back to the OAuth consent
screen and click Publish app (no verification is needed for the
adwordsscope used by your own account) — refresh tokens then stop expiring.
Step 3 — Generate the refresh token (one-time)
Easiest path — OAuth Playground:
- Open https://developers.google.com/oauthplayground.
- Click the ⚙️ gear (top right) → check "Use your own OAuth credentials" → paste your Client ID and Client Secret.
- In "Step 1" on the left, enter the scope manually:
https://www.googleapis.com/auth/adwords→ Authorize APIs. - Sign in with the Google account that owns the Ads manager account and consent.
- In "Step 2", click Exchange authorization code for tokens.
- Copy the Refresh token.
Alternative: curl-only flow (no Playground)
# 1. Open this URL in a browser, consent, and copy the code Google shows you:
open "https://accounts.google.com/o/oauth2/v2/auth?client_id=CLIENT_ID&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=https://www.googleapis.com/auth/adwords&access_type=offline&prompt=consent"
# 2. Exchange the code:
curl -s https://oauth2.googleapis.com/token \
-d client_id=CLIENT_ID -d client_secret=CLIENT_SECRET \
-d code=THE_CODE -d grant_type=authorization_code \
-d redirect_uri=urn:ietf:wg:oauth:2.0:oob
The JSON response contains refresh_token.
Step 4 — Customer IDs
GOOGLE_ADS_CUSTOMER_ID: the ID of the (non-manager) Ads account metrics are requested against. Shown top-right in the Ads UI as123-456-7890→ enter as1234567890(strip dashes).GOOGLE_ADS_LOGIN_CUSTOMER_ID: the manager account's ID — required whenever the customer account is accessed through the MCC (which is the normal setup here, since the developer token lives on the MCC). Same format, no dashes.
Step 5 — Configure and verify
Fill the block in .env:
GOOGLE_ADS_DEVELOPER_TOKEN=xxxxxxxxxxxxxxxxxxxxxx
GOOGLE_ADS_CLIENT_ID=xxxxxxxx.apps.googleusercontent.com
GOOGLE_ADS_CLIENT_SECRET=GOCSPX-xxxxxxxx
GOOGLE_ADS_REFRESH_TOKEN=1//xxxxxxxx
GOOGLE_ADS_CUSTOMER_ID=1234567890
GOOGLE_ADS_LOGIN_CUSTOMER_ID=9876543210
Verify end-to-end (should print a real volume map):
php artisan tinker --execute 'print_r(app(App\Services\Volumes\GoogleAdsClient::class)->getSearchVolumes(["crm software"], ["locationCode" => 2840, "languageCode" => "en"]));'
Expected shape:
["crm software" => ["volume" => 74000, "competitionIndex" => 89, "competition" => "HIGH"]]
Troubleshooting
| Error | Cause / fix |
|---|---|
DEVELOPER_TOKEN_NOT_APPROVED |
Token still at Test Account level — wait for Basic access approval (Step 1.5). |
invalid_grant on token refresh |
Refresh token expired (consent screen still in Testing → publish the app, Step 2.5) or was revoked — redo Step 3. |
USER_PERMISSION_DENIED |
The OAuth Google account can't access GOOGLE_ADS_CUSTOMER_ID, or GOOGLE_ADS_LOGIN_CUSTOMER_ID is missing/wrong for an MCC-linked account. |
CUSTOMER_NOT_FOUND |
Customer ID has dashes or is the MCC ID instead of the client account ID. |
| Metrics all zero | Brand-new Ads accounts with no billing history get bucketed/limited Keyword Planner data. Add billing info to the customer account (no spend required). |
Operational notes
- Quota: Basic access = 15,000 operations/day. One volume analysis ≈ 1 operation (up to 10,000 keywords per request), so quota is a non-issue.
- Token caching: the app caches the OAuth access token (~1 h) in the Laravel
cache; the refresh token itself is what lives in
.env. - Location/language codes: stored
location_codevalues (2840 = US, …) are Google's own geoTargetConstants IDs — unchanged from the DataForSEO era. Mapping lives inapp/Support/GoogleAdsCodes.php. - API version: the client pins
v21inGoogleAdsClient::API_BASE. Google sunsets versions roughly yearly; bump the constant when deprecation emails arrive.