Google Ads setup
Get Google Ads API access for the Cloud project behind the OAuth client now that developer tokens are gone, register the callback, set the API environment, and connect a first account.
How to give a deployment access to the Google Ads API, what to set in the API environment, and how to connect the first account.
Everything below was done for real on 2026-09-22 against the production Cloud project and a live Google Ads account: the access application, the OAuth connect, a full sync and a pause/enable round trip through the tool registry. The portal labels are the ones that were on screen that day.
There is no developer token any more
Google sunset developer tokens on 2026-09-09. They are no longer issued,
the developer-token header is ignored, and a future API version will reject
it. Older guides (and the Google Ads UI's old Tools → API Center page) still
describe the token application. Skip it.
Access now belongs to the Google Cloud project that owns the OAuth client, meaning the same client Google sign-in, the GA4 / Search Console connector and YouTube already use. There is no env var for it: the project either has an access level or it does not.
1. Brand-verify the Cloud project
Google Auth Platform → Branding. The right-hand Verification status panel must read "Your branding has been verified and is being shown to users". If it does not, submit it from there. It takes minutes, not days.
Brand verification is only required for Basic access and up, but the consent screen shows an unverified warning without it.
The consent screen is shared with sign-in, GA4/GSC and YouTube. Do not change its User type or Publishing status for Google Ads: either one changes all four flows, and a config change can re-trigger OAuth verification.
2. Enable the API and apply for access
- APIs & Services → Library → Google Ads API → Enable.
- On the API's page, under Access levels, click Manage. That opens
console.cloud.google.com/google/ads-apis/overview. - A fresh project sits at Test, which reaches test accounts only. Click Apply for access under Upgrade access level.
There is no form. The click submits the application, the page says it is "under review", and on 2026-09-22 Explorer was granted in about two minutes.
| Level | Production ops/day | How you get it |
|---|---|---|
| Test | 0 (test accounts) | default |
| Explorer | 2,880 | apply; granted automatically in minutes |
| Basic | 15,000 | apply; needs brand verification |
| Standard | unlimited | apply; manual audit |
Explorer is enough to run the product. The hourly incremental costs about 150 operations per account per day, so Explorer tops out near twenty accounts.
On 2026-09-24 the production project applied for Basic from the same page. With branding already verified, it is still one click and no form: the page said "under review", and Basic was granted within minutes. Production now has 15,000 operations a day and full API functionality. The next level, Standard, is a real application (Start application opens a form), and the page quotes a 10–15 business-day review.
3. Register the callback on the OAuth client
Google Auth Platform → Clients → the web client → Authorized redirect URIs → add:
https://<api-host>/integrations/google-ads/callbackIt sits alongside the sign-in, GA4/GSC and YouTube callbacks. For a local
run, add http://localhost:4000/integrations/google-ads/callback as well.
Google says a new URI can take from five minutes to a few hours to apply. On
2026-09-22 it worked within minutes.
A missing URI shows up as Google's redirect_uri_mismatch page, not as an
error in our logs.
4. API environment
| Variable | Value |
|---|---|
GOOGLE_OAUTH_CLIENT_ID | the shared web client (already set for sign-in) |
GOOGLE_OAUTH_CLIENT_SECRET | its secret |
GOOGLE_ADS_OAUTH_REDIRECT_URI | exactly the URI from step 3 |
GOOGLE_ADS_API_VERSION | optional; defaults to v25 |
Keep the API version current. Google sunsets a major version about a year
after release, and a sunset version answers every call with an HTML 404
rather than a JSON error. v21, the original default, was already gone on
2026-09-22 (v22 to v26 answered). To check a version without credentials:
curl -s -o /dev/null -w "%{http_code}\n" \
https://googleads.googleapis.com/v25/customers:listAccessibleCustomers
# 401 = the version exists; 404 = it is sunsetBumping it is a config change. Before bumping, check the release notes for
renamed fields: campaign.start_date / end_date, for example, became
start_date_time / end_date_time.
5. Connect an account
Settings → Integrations → Google Ads → Connect. Google asks for the
adwords scope, which covers both read and write because Google does not split
it. It is a separate grant from the GA4/GSC card's.
After consent the card lists every customer the login can reach. Managers (MCC accounts) are listed but cannot be picked. Pick one, and the historical backfill starts immediately. The card reads synced once it finishes.
Production was connected on 2026-09-23: the KeyesCode customer
(7960730539, USD, America/New_York, one Performance Max campaign) finished
its historical sync (historicalSyncCompleted), and on 2026-09-24 it read
syncStatus = synced, with hourly incrementals running.
The adwords scope and OAuth verification
adwords is a sensitive scope. Until 2026-09-24 it was missing from the
consent screen's Data Access list, which the verified scopes
(analytics.readonly, youtube.readonly, youtube.upload) came from. The
owner's own connect works regardless. Every other Google user sees the
unverified-app screen, and the project is held to the 100-user cap for
unverified scopes.
On 2026-09-24 adwords was added under Google Auth Platform → Data Access
and a justification saved, so the Verification Center now reads "data access
is not verified". Submitting needs a demo video, the only missing field.
The owner has to record it by hand: agent-recorded review videos get rejected
(see the TikTok audit). The video must show:
- the OAuth client ID in the address bar;
- the real consent screen listing the Google Ads permission;
- the customer picker;
- synced campaigns in the app;
- one approved write (for example pausing and re-enabling a campaign), and the same change shown in the Google Ads UI.
Then paste the unlisted YouTube link under Data Access → Demo video and submit from Verification Center → Prepare for verification. That page also carries an old "logo does not uniquely identify your brand" result with an optional appeal box. Branding is still verified.
What the first real run found
The code was written from Google's docs and had never touched the API before 2026-09-22. The first real run found five defects, all fixed in the same change:
- The pinned version was sunset. The default was
v21, which answered 404. pageSizewas removed in v19. Every page is now a fixed 10,000 rows, and single-row reads use GAQLLIMIT 1.campaign.start_date/end_datedo not exist in v25. The sync now readsstart_date_time/end_date_time, and campaign creation sends them.- Campaign creation must declare EU political advertising.
containsEuPoliticalAdvertisingis now sent on every create. - Performance Max was invisible. PMax serves from asset groups, has no
ad_group_adrows, and so had no metrics. Those campaigns are now also read at campaign level.
One scheduling defect also turned up. The sync job used a fixed BullMQ jobId,
and because a completed job is retained, every later "Sync now" and hourly tick
was silently dropped. The job now uses BullMQ deduplication, which holds only
while a job is waiting or running.
Something wrong or missing on this page? Tell us and we will fix it.