TL;DR
- You need a developer token (from the API Center), OAuth2 client (Google Cloud), and often a login-customer-id (your manager account) in requests.
- Read data with GAQL queries; mutate via service Operations (Campaign/AdGroup/Ad).
- Ship changes safely using validate_only (dry run) and partial_failure (commit valid ops, return errors for the rest).
- Use test accounts for sandboxing; watch quotas and implement retries with jitter.
- Track revenue with offline conversions (GCLID/GBRAID/WBRAID) or enhanced conversions.
1) Account & auth — what you actually need
- Manager (MCC) vs customer: a manager can access many child accounts; most apps call the API as a manager and set
login-customer-idto the manager’s ID. - Credentials checklist
- Developer token (apply in API Center of a manager account).
- OAuth2 client (client ID/secret) in Google Cloud.
- Refresh token for the Google Ads user.
- Login customer ID header when acting via a manager; set customer ID of the account you’re reading/writing.
Tip: start with a test manager + test client account so you can create campaigns freely.
2) Read with GAQL (Google Ads Query Language)
Common pattern
-- Last 7 days by campaign
SELECT
campaign.id,
campaign.name,
metrics.impressions,
metrics.clicks,
metrics.conversions,
metrics.cost_micros
FROM campaign
WHERE segments.date DURING LAST_7_DAYS
ORDER BY metrics.impressions DESC
LIMIT 50;
Python (search_stream)
from google.ads.googleads.client import GoogleAdsClient
client = GoogleAdsClient.load_from_storage() # google-ads.yaml
ga_service = client.get_service("GoogleAdsService")
query = """SELECT campaign.id, campaign.name, metrics.impressions
FROM campaign
WHERE segments.date DURING LAST_7_DAYS
LIMIT 10"""
stream = ga_service.search_stream(
customer_id="1234567890",
query=query,
# login_cust is set in config or via metadata
)
for batch in stream:
for row in batch.results:
print(row.campaign.id, row.campaign.name, row.metrics.impressions)
3) Create/update safely (validate, partial failure)
Dry‑run first
campaign_service = client.get_service("CampaignService")
op = client.get_type("CampaignOperation")
op.create.name = "API Campaign"
op.create.advertising_channel_type = client.enums.AdvertisingChannelTypeEnum.SEARCH
op.create.status = client.enums.CampaignStatusEnum.PAUSED
# validate_only=True: the API validates and returns errors, but makes no changes
resp = campaign_service.mutate_campaigns(
customer_id="1234567890", operations=[op], partial_failure=True, validate_only=True
)
print("Validated OK")
Then ship for real (keep partial failures on)
resp = campaign_service.mutate_campaigns(
customer_id="1234567890", operations=[op], partial_failure=True
)
for r in resp.results:
print("Created:", r.resource_name)
for e in resp.partial_failure_error.errors:
print("Failed op:", e.error_code)
Node (set headers & retry skeleton)
import { GoogleAdsApi } from "google-ads-api"; // community lib, or use official gRPC client
const client = new GoogleAdsApi({
client_id: process.env.GADS_CLIENT_ID,
client_secret: process.env.GADS_CLIENT_SECRET,
developer_token: process.env.GADS_DEV_TOKEN,
});
const customer = client.Customer({
customer_id: "1234567890",
login_customer_id: "1112223333", // manager MCC
refresh_token: process.env.GADS_REFRESH_TOKEN,
});
// GAQL read
const rows = await customer.query("SELECT campaign.id, campaign.name FROM campaign LIMIT 5");
console.log(rows);
// Mutate with simple retry/backoff (pseudo)
for (let attempt = 0, delay = 200; ; attempt++, delay = Math.min(2000, delay * 2)) {
try {
await customer.campaigns.create({ name: "API Campaign", status: "PAUSED", advertising_channel_type: "SEARCH" });
break;
} catch (e) {
if (!isRetryable(e) || attempt >= 2) throw e;
await new Promise(r => setTimeout(r, Math.random() * delay));
}
}
4) Offline conversions & enhanced conversions
- Import conversions that happen after the click (phone, CRM, POS) using GCLID/GBRAID/WBRAID and the ConversionUploadService.
- Use validate_only first; make sure the time zone and currency match your conversion action settings.
Python sketch (click conversions)
svc = client.get_service("ConversionUploadService")
conv = client.get_type("ClickConversion")
conv.gclid = "Cj0KCQi..." # or gbraid/wbraid for iOS/Android
conv.conversion_action = "customers/1234567890/conversionActions/9876543210"
conv.conversion_date_time = "2025-09-15 13:45:00-07:00"
conv.currency_code = "USD"
conv.conversion_value = 199.00
req = client.get_type("UploadClickConversionsRequest")
req.customer_id = "1234567890"
req.conversions.append(conv)
req.partial_failure = True
req.validate_only = True # flip to False to commit
resp = svc.upload_click_conversions(request=req)
print(resp)
5) Quotas, retries, and headers you must set
- Respect daily quota and per‑method limits; watch for rate/usage errors and back off with jitter.
- Typical headers/fields:
developer_token, OAuthAuthorization: Bearer, and when using a manager,login-customer-id. - Prefer search_stream for large GAQL reads; paginate where necessary.
- Log the request ID returned by the API to speed up support/debugging.
6) Pitfalls & fast fixes
| Pitfall | Why it hurts | Fix |
|---|---|---|
| Missing login-customer-id when using MCC | Auth succeeds but access denied | Set login_customer_id to your manager account |
| Mutations fail mid‑batch | One bad op aborts all | Turn on partial_failure; inspect returned errors |
| Surprises in prod | Schema/perm issues | Use test accounts and validate_only first |
| Wrong conversion timezone/currency | Conversions rejected or misvalued | Match conversion action settings; dry‑run first |
| Exceeding quota | 429/Rate errors | Exponential backoff + jitter; monitor usage |
| Hard‑coding IDs | Fragile code | Store IDs in config; fetch via GAQL in init step |
Quick checklist
- [ ] Developer token + OAuth client + refresh token created & stored securely.
- [ ] Using manager flow? Set login-customer-id in all requests.
- [ ] Reads via GAQL; large pulls use search_stream.
- [ ] Mutations go through validate_only first; keep partial_failure on.
- [ ] Offline conversions wired with ConversionUploadService.
- [ ] Quota + retries instrumented; log request IDs.
One‑minute adoption plan
- Create a test manager and test client; obtain a developer token and OAuth2 credentials.
- Run a GAQL read to verify access.
- Attempt a validate_only campaign creation; review errors.
- Turn on partial_failure and ship a real mutation.
- Hook up offline conversions in staging; verify in UI.