Google Ads API access for the dashboard
The Ads page of the dashboard reads spend, clicks, impressions, conversions and campaigns from the Axxya Systems Google Ads account (customer ID 926-442-1561, see Google Ads) through the Google Ads API. The code is backend/services/google_ads.py in the axxya-dashboard repository, which calls the REST endpoint directly and pins an API version. This page covers getting the API access and the credentials in place, and what to do when the Ads charts show a warning.
When to run this
- First-time setup of the Ads page in production (the Ads charts show
google_ads: ... missinguntil the credentials exist). - The refresh token stopped working (
invalid_grant,401), for example after the Google user's password or security settings changed. - The Cloud project's access level changed, or Google retired the pinned API version.
How access works now
Google changed the model on 9 September 2026 (developer tokens, access levels):
- Developer tokens and the old Ads "API Center" application are gone. The
developer-tokenheader is ignored and will be rejected by a future API version. The dashboard sends it only if a value is configured; leave it empty. - Access is a property of the Google Cloud project whose OAuth client is used. The dashboard uses the project
axxya-dashboard(the same one that holds the GA4 and Search Console service account). Its level is shown and changed on the Google Ads API Overview page. - Levels: Test (test accounts only, the state of a new project), Explorer (production accounts, 2,880 operations a day, one-click application), Basic (15,000 a day, needs brand verification of the project first), Standard (unlimited, manual review). The dashboard makes about 130 search operations a day across its six-hourly refreshes, so Explorer is enough.
- Four values reach the task as SSM secrets (
DASHBOARD_GOOGLE_ADS_CLIENT_ID,_CLIENT_SECRET,_REFRESH_TOKEN,_DEVELOPER_TOKEN), and the customer id is a plain setting incopilot/dashboard/dashboard.env.
Prerequisites
- Owner or editor access to the Google Cloud project
axxya-dashboard(sign in to the Cloud console ascawood@axxya.com). - A Google user that can open the Axxya Systems Google Ads account. The refresh token is issued to that user, so pick one that will keep access.
- A checkout of the dashboard repository with the backend virtualenv set up (
backend/.venv), and the deploy prerequisites from Deploying the dashboard.
Steps
-
Apply for Explorer access. Open the Google Ads API Overview page. The Google Ads API is already enabled in the project. Under "Upgrade access level" click Apply for access for Explorer and answer the short form. Google often grants Explorer automatically; wait until "Current access level" reads Explorer before continuing. Production calls made while the project is still at Test fail with
CLOUD_PROJECT_NOT_APPROVED_FOR_PRODUCTION. -
Create a Desktop app OAuth client. In the Cloud console go to Google Auth Platform > Clients > Create client, application type Desktop app, name it
Axxya Dashboard Ads (desktop). Copy the client ID and client secret. The existing "Axxya Dashboard Web client" cannot be used by the token script. -
Generate the refresh token. In the dashboard repository put the two values in the gitignored
.envasGOOGLE_ADS_CLIENT_IDandGOOGLE_ADS_CLIENT_SECRET, then run:cd ~/git/axxya/dashboardbackend/.venv/bin/pip install google-auth-oauthlibbackend/.venv/bin/python scripts/google_ads_refresh_token.pyA browser window opens. Sign in as the Google user from the prerequisites and allow the "Manage your AdWords campaigns" scope (the consent screen may warn that the app is unverified; continue). The script prints
GOOGLE_ADS_REFRESH_TOKEN=...; add that line to.env, together withGOOGLE_ADS_CUSTOMER_ID=9264421561(digits only). LeaveGOOGLE_ADS_DEVELOPER_TOKENandGOOGLE_ADS_LOGIN_CUSTOMER_IDempty. -
Check locally. Start the backend with the scheduler off and a scratch store directory so nothing is written to the production bucket, then request one report:
cd ~/git/axxya/dashboard/backendDASHBOARD_SCHEDULER=off DASHBOARD_STORE_BUCKET= DASHBOARD_STORE_DIR=/tmp/dashboard-store API_PORT=5077 .venv/bin/python app.pycurl -s 'http://127.0.0.1:5077/api/ads/metrics?range=two_months' | head -c 600The response should end with
"errors": []and show non-zerocost_usdon days with spend. Stop the backend afterwards. -
Create the four SSM secrets. From the repository root, one at a time, typing each value at the prompt (leave the other environments empty). The developer token is unused; give it the placeholder
unused:copilot secret init --name DASHBOARD_GOOGLE_ADS_CLIENT_IDcopilot secret init --name DASHBOARD_GOOGLE_ADS_CLIENT_SECRETcopilot secret init --name DASHBOARD_GOOGLE_ADS_REFRESH_TOKENcopilot secret init --name DASHBOARD_GOOGLE_ADS_DEVELOPER_TOKENAll four must exist before the next deploy: the manifest lists them, and ECS refuses to start the task if any one is missing.
-
Set the customer id for production. In
copilot/dashboard/dashboard.envsetGOOGLE_ADS_CUSTOMER_ID=9264421561and leaveGOOGLE_ADS_LOGIN_CUSTOMER_IDempty. -
Deploy as in Deploying the dashboard:
copilot svc deploy --name dashboard --env prod.
Verifying
- Open the Ads page at
https://dashboard.axxya.com/ads. The four charts show numbers and no amber warning. The chart footers show the time the data was built. - After the first background run (
report refresh finishedin the logs) the store holdsreports/ads/metrics/...andreports/ads/campaigns/...objects:aws s3 ls s3://nexgen-v3-dashboard-store/reports/ads/ --recursive. copilot svc logs --name dashboard --env prod --since 30m | grep -i "google_ads\|ads/"shows the requests without errors.
Troubleshooting
The Ads routes answer 200 with the failure in the chart's errors list, so look at the warning text on the chart rather than for HTTP errors.
Reporting source temporarily unavailable; retry shortly: the dashboard waits 30 seconds after a failed build of a report before trying again, and shows this in the meantime. Reload after half a minute to see the real reason.GOOGLE_ADS_... missing in .env: a required setting is empty in that environment. In production, check the four SSM parameters exist and thatdashboard.envhas the customer id, then redeploy.Google Ads API 403: ... CLOUD_PROJECT_NOT_APPROVED_FOR_PRODUCTION: the Cloud project is still at Test access. Finish step 1.invalid_grantorGoogle Ads API 401: the refresh token was revoked, or it was issued by a different OAuth client than the id and secret in use. Repeat step 3 and updateDASHBOARD_GOOGLE_ADS_REFRESH_TOKENwithcopilot secret init --name DASHBOARD_GOOGLE_ADS_REFRESH_TOKEN --overwrite, thencopilot svc deploy --name dashboard --env prod --force(secrets are read at task start).AUTHORIZATION_ERRORorUSER_PERMISSION_DENIED: the Google user behind the token cannot open that customer id, or the id is wrong. Check the account in Google Ads and the digits-only id.- A message naming a sunset or unsupported version: Google retires each major API version about a year after release. Bump
_API_VERSIONinbackend/services/google_ads.pyto the current version from the release notes and deploy. - The task never starts after the deploy (
ResourceInitializationError ... GetParameters): one of the four parameters is missing or mis-tagged; see Deploying the dashboard. RESOURCE_EXHAUSTED: the daily operation quota. The dashboard caches Ads responses for six hours and serves stored snapshots, so this should not happen at Explorer; if usage grows, Basic needs brand verification (Google Auth Platform > Branding: app name, home page, privacy policy, authorized domain, then Verify branding) before applying on the Overview page.
Related
- Deploying the dashboard
- How the dashboard loads its data
- Google Ads (the account and conversion tracking)