Google Ads
The Google Ads connector reaches https://googleads.googleapis.com/v26, the Google Ads API, as one Google login. Almost everything you would open the Google Ads UI to look up is one GAQL query away: what is running, what each campaign spent yesterday, which keywords convert, what changed last week. It can also create, change, pause and remove campaigns, budgets, ad groups, ads and targeting, and every one of those ships switched off and asks a human before it runs, because each can start or change spend.
What you need
Three things, and all three are yours rather than Permaura's.
1. A Google OAuth client. Google Ads is OAuth-only. Register a client the way the Google Drive page walks through: a Cloud console project, the consent screen, an OAuth client id and secret, and the redirect URI http://127.0.0.1:7376/oauth/callback for a gateway on your own machine or https://<your-gateway-host>/oauth/callback for a tunneled or hosted one. Enable the Google Ads API on the project. The one scope is https://www.googleapis.com/auth/adwords; it covers reads and writes alike, and the gateway's per-action switches are what keep the writes off.
A refresh token issued while the OAuth app's publishing status is Testing expires seven days after issuance, and refreshing it does not extend that. Set the app's user type to Internal (Google Workspace only) or publish it to In production; unverified is fine for your own use. The Drive page has the detail.
2. A developer token. Every Google Ads API call carries a developer-token header. You get one from the API Center of a Google Ads manager account (Tools and settings, Setup, API Center); a plain advertiser account has no API Center, so create a manager account and link your advertiser account under it if you have none. A new token has test account access and can only reach test accounts; apply for Basic access from the same page to read and change real accounts. Google reviews that application and it can take a few days.
3. The customer ids. A Google Ads account id is ten digits, shown as 123-456-7890 in the UI and sent as 1234567890, no dashes. Every action takes the account's id as customer_id. When the signed-in user reaches the account through a manager account, Google also wants that manager's id in a login-customer-id header; when the user is a direct member of the advertiser account, leave it out. customers.accessible lists the ids the signed-in user can reach directly, which is a good first call.
Add the connection
In the console, open Access, Connections, Add connection, pick Google Ads from the catalog, and fill in the Developer token field and, if you go through a manager account, the Login customer id. Under the credential choose OAuth, then Gateway-held · your own app, paste the client id and secret, check the redirect URI matches the one on your Google client, and choose Add. Google's consent page opens in a new tab; approve it as a user with access to the account, and your browser lands on the gateway's "Connected" page once the tokens are sealed.
The same flow runs against the gateway's REST API. The two headers travel as static_headers on the from-preset call:
BASE=http://127.0.0.1:7376
curl -s -X POST $BASE/v1/connections/from-preset \
-H @<(printf 'authorization: Bearer %s\n' "$TOKEN") -H 'content-type: application/json' -d '{
"preset_id": "google-ads",
"name": "ads-acme",
"static_headers": { "developer-token": "<your-developer-token>", "login-customer-id": "1234567890" }
}'
Then start the authorization against the returned connection_id exactly as on the Reddit page, with your Google client id and secret. The gateway refuses a from-preset call for this connector that carries no developer-token, since no call could succeed without it.
Where the developer token lives
The gateway normally refuses any per-connection header whose name looks like a credential, and developer-token does. The catalog's Google Ads definition declares the header as one every connection sends, and the gateway accepts a declared header for that connector alone. Here is what that means for you:
- The developer token is stored on the connection record on your gateway, in the clear, beside the pinned host, and anyone holding your gateway's operator token can read it there. Agents never see it; headers are not part of what a tool call returns.
- The token identifies your API access to Google. On its own it reaches nothing: every call also needs the OAuth access token of a user with access to the account, and that token is what the gateway seals and refreshes. A leaked developer token lets someone else spend your API quota under your name, which is worth rotating for, not an account takeover.
- Rotating it is a new connection, or a
from-presetcall with the new value, since the connection's headers are fixed when it is made.
If that is not the custody you want for it, hold off on this connector; the alternative, sealing the token into the vault beside the OAuth bundle, is a bigger change to the gateway's invoke path and is not built.
What the agent can do
| Action | What it does | On by default |
|---|---|---|
customers.accessible | Lists the customer ids the signed-in user can reach directly | Yes |
search | Runs a GAQL query, one page at a time | Yes |
search_stream | Runs a GAQL query and returns every row at once | Yes |
keyword_ideas.generate | Generates keyword ideas with volume and competition from seeds or a URL | Yes |
campaign_budgets.mutate | Creates, changes or removes campaign budgets | No, and needs approval |
campaigns.mutate | Creates, changes, pauses, enables or removes campaigns | No, and needs approval |
ad_groups.mutate | Creates, changes, pauses, enables or removes ad groups and their bids | No, and needs approval |
ad_group_ads.mutate | Creates, changes, pauses, enables or removes ads | No, and needs approval |
ad_group_criteria.mutate | Creates, changes or removes keywords and other ad group targeting | No, and needs approval |
campaign_criteria.mutate | Creates, changes or removes campaign targeting such as locations and negative keywords | No, and needs approval |
mutate | Applies a batch of operations across any resource types in one call | No, and needs approval |
Good to know
- Reads are POSTs, and that is fine. The Google Ads API takes a GAQL query in a JSON body, so Permaura's classifier would call
search,search_streamandkeyword_ideas.generatewrites and switch them off. The catalog marks the three as reads at medium risk so the connector answers questions the moment it is connected. Nothing they do changes the account. - One query language for everything.
searchtakescustomer_idandquery, for exampleSELECT campaign.id, campaign.name, campaign.status, metrics.cost_micros FROM campaign WHERE segments.date DURING LAST_7_DAYS. Costs come back in micros (divide by a million). The response carriesnextPageToken; pass it back aspageToken.search_streamreturns every row in one response and suits exports;searchsuits a conversation. Google's query builder lists the resources and fields. - Every change is a mutate. Each
*.mutateaction takesoperations, a list where each item is acreate, anupdatewith anupdateMask, or aremovenaming a resource. Pausing a campaign is anupdatesettingstatus: PAUSEDwithupdateMask: status; enabling it is the same call withENABLED, and both ask for an approval, since the second one starts spending.validateOnly: trueruns Google's checks without applying anything, which is a good thing for an agent to do before it asks a human to approve the real call. mutateis the widest write.googleAds:mutatetakesmutateOperationsacross any resource types in one atomic call. It is classified critical, above the per-resource mutates, so a policy can allow campaign changes without allowing everything.- Customer ids are digits only, in the path and in
login-customer-id. Google answers a dashed id with a 400 that names the id format. - The API version is pinned. The connector pins v26, the current version as of October 2026. Google releases new versions several times a year and retires each roughly a year after it ships; when v26 is retired, the Use a different host option on the connection form takes
https://googleads.googleapis.com/v27(or the version of the day) without a gateway update, because the host stays the same. - Personal fields are redacted. The
email_addressandinviter_user_email_addressfields on customer user access rows are blanked before the agent sees them. - A 403 on the first call is usually the developer token's access level, not a Permaura refusal: a test-account token against a real account, or Basic access not yet granted. The error body names
DEVELOPER_TOKEN_NOT_APPROVEDorDEVELOPER_TOKEN_PROHIBITEDwhen that is the cause.