caduh

Google Ads API — the essentials (tiny playbook)

4 min read

Developer token + OAuth, account hierarchy (manager vs customer), GAQL for reporting, safe mutations with validate_only/partial_failure, quotas, and offline conversions.

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-id to 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, OAuth Authorization: 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

  1. Create a test manager and test client; obtain a developer token and OAuth2 credentials.
  2. Run a GAQL read to verify access.
  3. Attempt a validate_only campaign creation; review errors.
  4. Turn on partial_failure and ship a real mutation.
  5. Hook up offline conversions in staging; verify in UI.