Google Ads API Setup
Complete guide for setting up Google Ads API access.
Table of Contents
Prerequisites
What You Need
- Google Ads account (or MCC access)
- Developer token - Apply at developers.google.com/google-ads/api/docs/first-call/dev-token
- OAuth 2.0 credentials - Create in Google Cloud Console
- Python 3.8+ with
google-adspackage
Install SDK
pip install google-ads
# Or with venv
python -m venv google-ads-venv
source google-ads-venv/bin/activate
pip install google-ads
OAuth Setup
1. Create OAuth Credentials
1. Go to console.cloud.google.com
2. Create project (or select existing)
3. Enable "Google Ads API"
4. APIs & Services → Credentials
5. Create Credentials → OAuth Client ID
6. Application type: Desktop app
7. Download JSON (client_secret_*.json)
2. Generate Refresh Token
# oauth_setup.py
from google_auth_oauthlib.flow import InstalledAppFlow
SCOPES = ['https://www.googleapis.com/auth/adwords']
flow = InstalledAppFlow.from_client_secrets_file(
'client_secret.json', scopes=SCOPES)
# Opens browser for consent
credentials = flow.run_local_server(port=8080)
print(f"Refresh token: {credentials.refresh_token}")
3. Get Login Customer ID
- For single account: Your 10-digit account ID
- For MCC: The MCC account ID (for API auth)
- Customer ID: The account you're querying (can differ from login)
Configuration File
google-ads.yaml
developer_token: YOUR_DEVELOPER_TOKEN
client_id: YOUR_CLIENT_ID.apps.googleusercontent.com
client_secret: YOUR_CLIENT_SECRET
refresh_token: YOUR_REFRESH_TOKEN
login_customer_id: YOUR_MCC_ID # Optional, for MCC accounts
# Optional settings
# use_proto_plus: True
# logging:
# version: 1
# handlers:
# default:
# class: logging.StreamHandler
# loggers:
# "":
# level: WARNING
Location Options
1. ~/.google-ads.yaml (default)
2. ./google-ads.yaml (project directory)
3. Environment variables:
- GOOGLE_ADS_DEVELOPER_TOKEN
- GOOGLE_ADS_CLIENT_ID
- GOOGLE_ADS_CLIENT_SECRET
- GOOGLE_ADS_REFRESH_TOKEN
- GOOGLE_ADS_LOGIN_CUSTOMER_ID
Common Queries
Initialize Client
from google.ads.googleads.client import GoogleAdsClient
# From file
client = GoogleAdsClient.load_from_storage("./google-ads.yaml")
# Or from env
client = GoogleAdsClient.load_from_env()
ga_service = client.get_service("GoogleAdsService")
Campaign Performance
CUSTOMER_ID = "1234567890" # No dashes
query = """
SELECT
campaign.id,
campaign.name,
campaign.status,
campaign_budget.amount_micros,
metrics.cost_micros,
metrics.conversions,
metrics.cost_per_conversion
FROM campaign
WHERE segments.date DURING LAST_30_DAYS
AND campaign.status != 'REMOVED'
ORDER BY metrics.cost_micros DESC
LIMIT 50
"""
response = ga_service.search(customer_id=CUSTOMER_ID, query=query)
for row in response:
cost = row.metrics.cost_micros / 1_000_000
conv = row.metrics.conversions
cpa = row.metrics.cost_per_conversion / 1_000_000 if conv > 0 else 0
print(f"{row.campaign.name}: ${cost:.2f} | {conv:.1f} conv | ${cpa:.2f} CPA")
Zero-Conversion Keywords
query = """
SELECT
ad_group_criterion.criterion_id,
ad_group_criterion.keyword.text,
ad_group_criterion.keyword.match_type,
campaign.name,
ad_group.name,
metrics.cost_micros,
metrics.clicks,
metrics.impressions
FROM keyword_view
WHERE metrics.conversions = 0
AND metrics.cost_micros > 500000000
AND segments.date DURING LAST_90_DAYS
AND ad_group_criterion.status = 'ENABLED'
ORDER BY metrics.cost_micros DESC
LIMIT 100
"""
Ad Group Status Check
query = """
SELECT
ad_group.id,
ad_group.name,
ad_group.status,
campaign.name,
metrics.cost_micros
FROM ad_group
WHERE ad_group.status = 'ENABLED'
AND segments.date DURING LAST_30_DAYS
"""
# Check for ad groups with no ads
query_ads = """
SELECT
ad_group.id,
ad_group.name,
ad_group_ad.ad.id
FROM ad_group_ad
WHERE ad_group.status = 'ENABLED'
AND ad_group_ad.status = 'ENABLED'
"""
Conversion Actions
query = """
SELECT
conversion_action.id,
conversion_action.name,
conversion_action.status,
conversion_action.category,
conversion_action.counting_type,
metrics.conversions,
metrics.all_conversions
FROM conversion_action
"""
Mutation Operations
Pause Campaigns
from google.ads.googleads.client import GoogleAdsClient
client = GoogleAdsClient.load_from_storage()
campaign_service = client.get_service("CampaignService")
def pause_campaigns(customer_id, campaign_ids):
operations = []
for campaign_id in campaign_ids:
operation = client.get_type("CampaignOperation")
campaign = operation.update
campaign.resource_name = campaign_service.campaign_path(
customer_id, campaign_id
)
campaign.status = client.enums.CampaignStatusEnum.PAUSED
client.copy_from(
operation.update_mask,
protobuf_helpers.field_mask(None, campaign._pb)
)
operations.append(operation)
response = campaign_service.mutate_campaigns(
customer_id=customer_id,
operations=operations
)
return response
Pause Keywords
ad_group_criterion_service = client.get_service("AdGroupCriterionService")
def pause_keywords(customer_id, keyword_resource_names):
operations = []
for resource_name in keyword_resource_names:
operation = client.get_type("AdGroupCriterionOperation")
criterion = operation.update
criterion.resource_name = resource_name
criterion.status = client.enums.AdGroupCriterionStatusEnum.PAUSED
# Set field mask
client.copy_from(
operation.update_mask,
protobuf_helpers.field_mask(None, criterion._pb)
)
operations.append(operation)
response = ad_group_criterion_service.mutate_ad_group_criteria(
customer_id=customer_id,
operations=operations
)
return response
Update Budget
campaign_budget_service = client.get_service("CampaignBudgetService")
def update_budget(customer_id, budget_id, new_amount_micros):
operation = client.get_type("CampaignBudgetOperation")
budget = operation.update
budget.resource_name = campaign_budget_service.campaign_budget_path(
customer_id, budget_id
)
budget.amount_micros = new_amount_micros
client.copy_from(
operation.update_mask,
protobuf_helpers.field_mask(None, budget._pb)
)
response = campaign_budget_service.mutate_campaign_budgets(
customer_id=customer_id,
operations=[operation]
)
return response
Error Handling
Common Errors
from google.ads.googleads.errors import GoogleAdsException
try:
response = ga_service.search(customer_id=CUSTOMER_ID, query=query)
except GoogleAdsException as ex:
for error in ex.failure.errors:
print(f"Error: {error.message}")
print(f"Code: {error.error_code}")
if error.location:
for field_path_element in error.location.field_path_elements:
print(f" Field: {field_path_element.field_name}")
Rate Limits
- Standard: 15,000 operations/day (mutate)
- Query: Generally unlimited but be reasonable
- Retry with exponential backoff on RESOURCE_EXHAUSTED