NotFair-GoogleAds
Server Details
Google Ads MCP - analyze performance, manage keywords, bids, and campaigns.
- Status
- Unhealthy
- Last Tested
- Transport
- Streamable HTTP
- URL
- Repository
- nowork-studio/toprank
- GitHub Stars
- 499
Glama MCP Gateway
Connect through Glama MCP Gateway for full control over tool access and complete visibility into every call.
Full call logging
Every tool call is logged with complete inputs and outputs, so you can debug issues and audit what your agents are doing.
Tool access control
Enable or disable individual tools per connector, so you decide what your agents can and cannot do.
Managed credentials
Glama handles OAuth flows, token storage, and automatic rotation, so credentials never expire on your clients.
Usage analytics
See which tools your agents call, how often, and when, so you can understand usage patterns and catch anomalies.
Tool Definition Quality
Score is being calculated. Check back soon.
Available Tools
89 toolsaddExperimentArmsIdempotentInspect
Step 2 of 5. Create both arms (control + treatment) in ONE atomic call — Google forbids adding arms incrementally because traffic_split must sum to 100. The control arm references an existing campaign; the treatment arm has Google auto-spawn a trial campaign that you then mutate (returned as inDesignCampaigns[0]). Returns the trial campaign resource name(s) so the agent can apply the change under test BEFORE scheduling. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| arms | Yes | Provide both arms in one call. v1 supports exactly one control + one treatment (Google's current limit). | |
| accountId | No | Account ID (omit for primary) | |
| experimentResourceName | Yes | Resource name from createExperiment, e.g. 'customers/123/experiments/456'. |
addKeywordIdempotentInspect
Create/add a new positive keyword in an ad group (starts enabled). Use this for a single new keyword; use bulkAddKeywords to create many positive keywords at once. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| keyword | Yes | ||
| accountId | No | Account ID (omit for primary) | |
| adGroupId | Yes | ||
| matchType | No | BROAD | |
| campaignId | Yes | ||
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
addKeywordToNegativeListIdempotentInspect
Add a keyword to a shared negative keyword list. The keyword will be blocked across all campaigns linked to this list. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| keyword | Yes | Keyword text to block | |
| accountId | No | Account ID (omit for primary) | |
| matchType | No | Match type (default: PHRASE) | PHRASE |
| sharedSetId | Yes | Shared set ID (query shared_set WHERE type = NEGATIVE_KEYWORDS via runScript) |
addNegativeKeywordIdempotentInspect
Add a negative keyword to a campaign. Also use this to re-enable a previously removed negative keyword (Google Ads has no 'enable' state for negatives). Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| keyword | Yes | Keyword text to block | |
| accountId | No | Account ID (omit for primary) | |
| matchType | No | Match type for the negative keyword (default: PHRASE) | PHRASE |
| campaignId | Yes | ||
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
askSupportInspect
Contact NotFair support. Use this tool when the user explicitly wants to reach the support team — for example, they say "contact support", "file a bug", "report an issue", "I need help from the NotFair team", or "this is a NotFair problem not a Google Ads problem".
This sends a message directly to the NotFair team and generates a ticket. The user will receive a response via email within 1 business day.
DO NOT use this for:
Routine Google Ads questions you can answer yourself.
Internal tool quality issues — use fileInternalNotFairToolFeedback for those.
Questions you haven't tried to answer yet.
Only call this when the user has explicitly asked to contact support, or when you've exhausted your ability to help and the user agrees escalation is the right move.
| Name | Required | Description | Default |
|---|---|---|---|
| context | No | Brief context about what the user was trying to do. Omit PII except what the user explicitly included. | |
| message | Yes | The user's message to the support team. Write it in first person as if the user wrote it. |
bulkAddKeywordsIdempotentInspect
Bulk-create/add up to 100 new positive keywords to an ad group in one call. This is the bulk variant of addKeyword/create keyword. Atomic by default: the server pre-validates every item and executes nothing if any keyword fails static checks such as duplicates, invalid syntax, removed parents, or negative-keyword conflicts. Set continueOnError=true to skip invalid items and add the valid subset. Set dryRun=true to validate only. Returns per-keyword results with individual changeIds when executed.
| Name | Required | Description | Default |
|---|---|---|---|
| dryRun | No | If true, run pre-validation but do not execute. Returns wouldSucceedIds and structured errors/warnings. | |
| keywords | Yes | ||
| accountId | No | Account ID (omit for primary) | |
| adGroupId | Yes | ||
| campaignId | Yes | Campaign ID (for logging) | |
| continueOnError | No | If true, skip invalid items and execute the valid subset. If false, fail the whole batch before writing when any item fails pre-validation. | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
bulkPauseKeywordsDestructiveIdempotentInspect
Pause up to 100 POSITIVE keywords in one call. Atomic by default: the server pre-validates every item and executes nothing if any item fails static checks. Does NOT work on negative keywords — for negatives, call removeNegativeKeyword or removeKeywordFromNegativeList; Google Ads has no 'pause' for negatives. Set continueOnError=true to skip invalid items and pause the valid subset. Set dryRun=true to validate only. Returns per-keyword results with individual changeIds when executed.
| Name | Required | Description | Default |
|---|---|---|---|
| dryRun | No | Validate and report what would happen without writing to Google Ads or logging changes. | |
| keywords | Yes | ||
| accountId | No | Account ID (omit for primary) | |
| continueOnError | No | If true, skip invalid items and execute the valid subset. If false, fail the whole batch before writing when any item fails pre-validation. | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
bulkUpdateBidsDestructiveIdempotentInspect
Update up to 50 keyword bids in one call. Atomic by default: the server pre-validates every item and executes nothing if any item fails static checks. Set continueOnError=true to skip invalid items and update the valid subset. Set dryRun=true to validate only. Each bid change is capped by the per-account maxBidChangePct guardrail (default 25%); raise it with setGuardrails (max 100% per call) and confirm with the user before stepping bigger — for multi-step ramps, iterate (e.g. 100% → 100% to go 4× in two calls). Returns per-keyword results with individual changeIds when executed.
| Name | Required | Description | Default |
|---|---|---|---|
| dryRun | No | If true, run pre-validation but do not execute. Returns wouldSucceedIds and structured errors/warnings. | |
| updates | Yes | ||
| accountId | No | Account ID (omit for primary) | |
| continueOnError | No | If true, skip invalid items and execute the valid subset. If false, fail the whole batch before writing when any item fails pre-validation. | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
createAdIdempotentInspect
Create a Responsive Search Ad (RSA) in an ad group. Optionally include path1/path2 for the display URL (the segments shown after the domain, e.g. example.com/path1/path2). Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| path1 | No | Display URL path 1 (max 15 chars, no spaces). Shown after the domain. | |
| path2 | No | Display URL path 2 (max 15 chars, no spaces). Requires path1. | |
| finalUrl | Yes | ||
| accountId | No | Account ID (omit for primary) | |
| adGroupId | Yes | ||
| headlines | Yes | 3-15 headlines, max 30 chars each | |
| campaignId | Yes | Campaign ID (for logging/undo tracking) | |
| descriptions | Yes | 2-4 descriptions, max 90 chars each | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
createAdGroupIdempotentInspect
Create an ad group in a campaign (starts enabled). Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| campaignId | Yes | ||
| adGroupName | Yes | ||
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
createAdVariationExperimentIdempotentInspect
RSA-asset A/B test shortcut. Bundles createExperiment + addExperimentArms + asset patch on the trial RSA into ONE call. Use to A/B-test an RSA's headlines, descriptions, or final URL against the live version. Internally a SEARCH_CUSTOM experiment whose treatment-arm clone has its RSA patched — Google's verified API path for RSA A/B testing. The base RSA is cloned into a trial campaign; this tool patches the clone and leaves the experiment in SETUP — you call scheduleExperiment to begin serving. Required: at least one of headlines, descriptions, finalUrl. RSA assets are atomic — when patching copy, supply BOTH headlines AND descriptions (Google replaces the full asset set). Returns experimentResourceName, trialCampaignId, trialAdGroupId, trialAdId, and readyToSchedule. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Experiment name, unique under the customer. | |
| suffix | No | Trial campaign name suffix. Defaults to '[ad-var]'. | |
| endDate | No | ||
| baseAdId | Yes | RSA ID to clone and vary. Must be a Responsive Search Ad. | |
| finalUrl | No | Replacement landing page URL for the trial RSA. | |
| accountId | No | Account ID (omit for primary) | |
| headlines | No | Replacement headlines for the trial RSA (3–15, ≤30 chars). Omit to keep the original headlines. | |
| startDate | No | ||
| description | No | ||
| descriptions | No | Replacement descriptions for the trial RSA (2–4, ≤90 chars). Omit to keep originals. If you pass headlines you MUST also pass descriptions (RSA assets are atomic). | |
| baseAdGroupId | Yes | Ad group ID containing the base RSA. | |
| baseCampaignId | Yes | Existing campaign ID containing the RSA you want to vary. | |
| treatmentTrafficSplit | No | Percent of traffic routed to the variation (1–99). Default 50 (50/50). |
createAppCampaignIdempotentInspect
Create an App campaign (install-focused) for the Apple App Store or Google Play Store. App ID required. Add image and video assets in Google Ads UI for full serving. Starts PAUSED. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| appId | Yes | App ID: Apple App Store numeric ID (e.g. '123456789') or Google Play package name (e.g. 'com.example.app'). | |
| bidding | No | Bidding configuration. Defaults to TARGET_CPA. | |
| appStore | Yes | App store: GOOGLE_APP_STORE for Android, APPLE_APP_STORE for iOS. | |
| finalUrl | Yes | App store URL (e.g. https://apps.apple.com/app/id123456789). | |
| accountId | No | Account ID (omit for primary) | |
| headlines | Yes | 2–5 headlines, max 30 chars each. | |
| languageIds | No | Language constant IDs. Defaults to no restriction. | |
| businessName | No | Business name shown in ads. | |
| campaignName | Yes | ||
| descriptions | Yes | 1–5 descriptions, max 90 chars each. | |
| geoTargetIds | No | Geo target constant IDs (e.g. '2840' for US). Use searchGeoTargets to find IDs. | |
| dailyBudgetDollars | Yes | Daily budget in dollars |
createBiddingStrategyIdempotentInspect
Create a portfolio bidding strategy — a shared bidding configuration that multiple campaigns can reference. Supports TARGET_CPA, TARGET_ROAS, MAXIMIZE_CONVERSIONS, and MAXIMIZE_CONVERSION_VALUE. For TARGET_CPA, targetCpa (in dollars) is required. For TARGET_ROAS, targetRoas (e.g. 2.0 = 200%) is required. Returns changeId + biddingStrategyId. Use linkCampaignToBiddingStrategy to attach to campaigns.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Strategy name, e.g. 'Lead Gen Target CPA' | |
| type | Yes | ||
| accountId | No | Account ID (omit for primary) | |
| targetCpa | No | Target CPA in dollars. Required for TARGET_CPA; optional cap for MAXIMIZE_CONVERSIONS. | |
| targetRoas | No | Target ROAS multiplier (e.g. 2.0 = 200% return). Required for TARGET_ROAS; optional cap for MAXIMIZE_CONVERSION_VALUE. |
createCallAssetIdempotentInspect
Create a call asset (phone number + country code) and optionally link it to customer/campaign/ad-group targets in the same atomic mutate. Call assets show a phone number in search ads and enable call tracking. callConversionReportingState defaults to account-level tracking when omitted. Returns changeId, assetId, and link resource names.
| Name | Required | Description | Default |
|---|---|---|---|
| targets | No | Optional serving targets (customer/campaign/ad_group — call assets don't support asset_group). Omit or pass [] to create the asset only. Array of targets. Each target is an OBJECT with a `level` discriminator — bare resource strings (e.g. "customers/123") are rejected. Shapes: { level: 'customer' } (account-wide), { level: 'campaign', campaignId: '123' }, { level: 'ad_group', adGroupId: '456' }, { level: 'asset_group', assetGroupId: '789' } (Performance Max only). Example: [{ level: 'customer' }, { level: 'campaign', campaignId: '12345' }]. | |
| accountId | No | Account ID (omit for primary) | |
| countryCode | Yes | Two-letter ISO 3166-1 alpha-2 country code, e.g. 'US' | |
| phoneNumber | Yes | Phone number in E.164-style or local format, e.g. '+14155550123' or '(415) 555-0123' | |
| callConversionAction | No | Conversion action resource_name to use when callConversionReportingState is USE_RESOURCE_LEVEL_CALL_CONVERSION_ACTION. Format: customers/{customer_id}/conversionActions/{id}. | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. | |
| callConversionReportingState | No | Call conversion reporting behavior. Omit to use account-level tracking (default). |
createCalloutAssetIdempotentInspect
Create a callout asset (≤25 char snippet shown under text ads, e.g. 'Free shipping'). Optionally link it to customer, campaign, or ad group targets in the same atomic mutate via targets. Returns changeId, assetId, and link resource names. To attach an existing callout to more targets later, call linkAsset.
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes | Callout text (≤25 chars), e.g. 'Free shipping' | |
| targets | No | Optional serving targets. Omit or pass [] to create the asset only; pass targets to link it in the same mutate. Array of targets. Each target is an OBJECT with a `level` discriminator — bare resource strings (e.g. "customers/123") are rejected. Shapes: { level: 'customer' } (account-wide), { level: 'campaign', campaignId: '123' }, { level: 'ad_group', adGroupId: '456' }, { level: 'asset_group', assetGroupId: '789' } (Performance Max only). Example: [{ level: 'customer' }, { level: 'campaign', campaignId: '12345' }]. | |
| accountId | No | Account ID (omit for primary) | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
createCampaignIdempotentInspect
Create a Search campaign with budget, ad group, keywords, and a Responsive Search Ad. Starts PAUSED — use enableCampaign to go live. Returns changeId. For other campaign types use: createShoppingCampaign, createPerformanceMaxCampaign, createDemandGenCampaign, createDisplayCampaign, createVideoCampaign, createAppCampaign.
| Name | Required | Description | Default |
|---|---|---|---|
| bidding | No | Bidding configuration. Defaults to MAXIMIZE_CONVERSIONS. | |
| finalUrl | Yes | Primary landing page URL. | |
| keywords | Yes | Keywords to target (at least 1 required). | |
| accountId | No | Account ID (omit for primary) | |
| headlines | Yes | 3–15 headlines, max 30 chars each. | |
| languageIds | No | Language constant IDs (e.g. '1000' for English). Defaults to no restriction. | |
| campaignName | Yes | ||
| descriptions | Yes | 2–4 descriptions, max 90 chars each. | |
| geoTargetIds | No | Geo target constant IDs (e.g. '2840' for US). Use searchGeoTargets to find IDs. | |
| keywordMatchType | No | Keyword match type. Defaults to BROAD. | |
| dailyBudgetDollars | Yes | Daily budget in dollars |
createConversionActionIdempotentInspect
Create a conversion action for tracking offline conversions (imports), web events, or calls. Optionally enable Enhanced Conversions for Leads (ECFL) for user-data matching. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Conversion action name, e.g. 'First Booking' | |
| type | No | UPLOAD_CLICKS for offline/import conversions, WEBPAGE for website events, UPLOAD_CALLS for call tracking | UPLOAD_CLICKS |
| status | No | ENABLED | |
| category | No | PURCHASE | |
| accountId | No | Account ID (omit for primary) | |
| countingType | No | ONE_PER_CLICK counts one conversion per click (leads), MANY_PER_CLICK counts every conversion (purchases) | ONE_PER_CLICK |
| defaultValue | No | Default conversion value in account currency | |
| primaryForGoal | No | true = primary (included in Conversions column for bidding), false = secondary (observation only) | |
| alwaysUseDefaultValue | No | Always use default value vs. transaction-specific values | |
| enhancedConversionsForLeads | No | Enable Enhanced Conversions for Leads at account level. Requires customer data terms to be accepted in Google Ads UI first. | |
| viewThroughLookbackWindowDays | No | View-through conversion lookback window (1-30 days) | |
| clickThroughLookbackWindowDays | No | Click-through conversion lookback window (1-90 days) |
createDemandGenCampaignIdempotentInspect
Create a Demand Gen campaign serving on YouTube/Gmail/Discover. Asset-based discovery campaigns. Add image assets in Google Ads UI for full ad delivery. Starts PAUSED. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| bidding | No | Bidding configuration. Defaults to MAXIMIZE_CONVERSIONS. | |
| finalUrl | Yes | Primary landing page URL. | |
| accountId | No | Account ID (omit for primary) | |
| headlines | Yes | 3–5 headlines, max 40 chars each. | |
| languageIds | No | Language constant IDs. Defaults to no restriction. | |
| businessName | Yes | Business name shown in ads. | |
| campaignName | Yes | ||
| descriptions | Yes | 2–5 descriptions, max 90 chars each. | |
| geoTargetIds | No | Geo target constant IDs (e.g. '2840' for US). Use searchGeoTargets to find IDs. | |
| longHeadlines | Yes | 1–5 long headlines, max 90 chars each. | |
| dailyBudgetDollars | Yes | Daily budget in dollars |
createDisplayCampaignIdempotentInspect
Create a Display Network campaign with a Responsive Display Ad. Image assets must be uploaded first via createImageAsset; pass the resulting asset resource names. Starts PAUSED. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| bidding | No | Bidding configuration. Defaults to MAXIMIZE_CONVERSIONS. | |
| finalUrl | Yes | Primary landing page URL. | |
| accountId | No | Account ID (omit for primary) | |
| headlines | Yes | 1–5 short headlines, max 30 chars each. | |
| adGroupName | No | Ad group name. Defaults to '{campaignName} - Ad Group 1'. | |
| languageIds | No | Language constant IDs. Defaults to no restriction. | |
| businessName | Yes | Business name shown in ads. | |
| campaignName | Yes | ||
| descriptions | Yes | 1–5 descriptions, max 90 chars each. | |
| geoTargetIds | No | Geo target constant IDs (e.g. '2840' for US). Use searchGeoTargets to find IDs. | |
| longHeadline | Yes | Single long headline, max 90 chars. | |
| logoImageAssetId | No | Optional logo image asset resource name. | |
| dailyBudgetDollars | Yes | Daily budget in dollars | |
| marketingImageAssetId | Yes | Asset resource name for landscape marketing image (1200×628). Create via createImageAsset first. | |
| squareMarketingImageAssetId | Yes | Asset resource name for square marketing image (1200×1200). Create via createImageAsset first. |
createExperimentIdempotentInspect
Create a Google Ads experiment in SETUP status. Step 1 of 5 — next call addExperimentArms with one control + one treatment arm. Type SEARCH_CUSTOM for general search experiments (compare ads/keywords/landing pages); SEARCH_AUTOMATED_BIDDING_STRATEGY to compare bidding strategies on the same campaign. The experiment doesn't serve traffic until scheduleExperiment is called. Returns experimentResourceName.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Experiment name, unique under the customer. | |
| type | Yes | SEARCH_CUSTOM for ad/keyword/landing-page tests; SEARCH_AUTOMATED_BIDDING_STRATEGY to compare bidding strategies. | |
| suffix | No | String appended to the trial campaign name. Defaults to '[experiment]'. | |
| endDate | No | YYYY-MM-DD. Defaults to the base campaign's end date. Recommended: ≥14 days after start for stat significance. | |
| accountId | No | Account ID (omit for primary) | |
| startDate | No | YYYY-MM-DD. Defaults to today (or campaign start, whichever is later). | |
| description | No | ||
| syncEnabled | No | If true, edits to the base campaign also propagate into the trial. Immutable after creation. |
createImageAssetIdempotentInspect
Upload a PNG/JPEG image asset from an HTTPS URL. Pick the field type by SERVING SLOT, not by aspect ratio: MARKETING_IMAGE (Display/PMax 1.91:1, min 600x314) | SQUARE_MARKETING_IMAGE (Display/PMax 1:1, min 300x300) | AD_IMAGE (Search/Display 'image extension' on RSAs — accepts either 1.91:1 OR 1:1 source, campaign/ad_group link levels only). Optionally link it to serving targets via targets. Returns changeId, assetId, and link resource names. To attach an existing image to more targets later, call linkAsset.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Asset name shown in Google Ads, e.g. 'Spring promo landscape' | |
| targets | No | Optional serving targets. MARKETING_IMAGE/SQUARE_MARKETING_IMAGE support all 4 levels (customer/campaign/ad_group/asset_group for Performance Max). AD_IMAGE supports campaign/ad_group only. Omit or pass [] to create the asset only. Array of targets. Each target is an OBJECT with a `level` discriminator — bare resource strings (e.g. "customers/123") are rejected. Shapes: { level: 'customer' } (account-wide), { level: 'campaign', campaignId: '123' }, { level: 'ad_group', adGroupId: '456' }, { level: 'asset_group', assetGroupId: '789' } (Performance Max only). Example: [{ level: 'customer' }, { level: 'campaign', campaignId: '12345' }]. | |
| imageUrl | Yes | Public HTTPS URL for the PNG/JPEG image to upload. Max 5 MB. | |
| accountId | No | Account ID (omit for primary) | |
| fieldType | Yes | Serving slot; pre-validates dimensions and (when `targets` is set) used as the link field_type. AD_IMAGE accepts either 1.91:1 or 1:1 source dimensions. | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
createNegativeKeywordListIdempotentInspect
Create a shared negative keyword list. After creating, add keywords with addKeywordToNegativeList and link to campaigns with linkNegativeListToCampaign. Returns changeId + sharedSetId.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | List name, e.g. 'Brand Negatives' or 'Competitor Terms' | |
| accountId | No | Account ID (omit for primary) |
createPerformanceMaxCampaignIdempotentInspect
Create a Performance Max campaign that serves across all Google channels via asset groups. Pass merchantId+salesCountry for retail PMax linked to Merchant Center. Starts PAUSED. Add image and video assets in Google Ads UI before enabling for full serving scale. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| bidding | No | Bidding configuration. Defaults to MAXIMIZE_CONVERSIONS. | |
| finalUrl | Yes | Primary landing page URL. | |
| accountId | No | Account ID (omit for primary) | |
| headlines | Yes | 3–15 headlines, max 30 chars each. | |
| merchantId | No | Google Merchant Center account ID. Optional — links to product feed for retail PMax. | |
| languageIds | No | Language constant IDs. Defaults to no restriction. | |
| businessName | Yes | Business name shown in ads. | |
| campaignName | Yes | ||
| descriptions | Yes | 2–5 descriptions, max 90 chars each. | |
| geoTargetIds | No | Geo target constant IDs (e.g. '2840' for US). Use searchGeoTargets to find IDs. | |
| salesCountry | No | ISO-3166-1 alpha-2 sales country (e.g. 'US'). Required when merchantId is provided. | |
| longHeadlines | Yes | 1–5 long headlines, max 90 chars each. | |
| dailyBudgetDollars | Yes | Daily budget in dollars |
createShoppingCampaignIdempotentInspect
Create a Standard Shopping campaign linked to a Merchant Center feed. Optional inventoryFilter scopes the campaign to a product_type or custom_label. Starts PAUSED. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| bidding | No | Bidding configuration. Defaults to MANUAL_CPC. | |
| accountId | No | Account ID (omit for primary) | |
| merchantId | Yes | Google Merchant Center account ID. | |
| enableLocal | No | Enable local inventory ads. Defaults to false. | |
| languageIds | No | Ignored for Standard Shopping: Google Ads does not support language campaign criteria on Shopping campaigns. Use Merchant Center feed language/feed label plus salesCountry and geoTargetIds instead. | |
| campaignName | Yes | ||
| geoTargetIds | No | Geo target constant IDs (e.g. '2840' for US). Use searchGeoTargets to find IDs. | |
| salesCountry | Yes | ISO-3166-1 alpha-2 sales country (e.g. 'US'). | |
| searchPartners | No | Include search partner network. Defaults to false. | |
| inventoryFilter | No | Inventory filter dimensions restricting campaign to matching products. Omit to show all products. | |
| campaignPriority | No | Campaign priority: 0=LOW (default), 1=MEDIUM, 2=HIGH. | |
| dailyBudgetDollars | Yes | Daily budget in dollars |
createSitelinkAssetIdempotentInspect
Create a sitelink asset (link text + destination URL + optional description pair). Optionally link it to customer/campaign/ad-group targets via targets. Sitelink text ≤25 chars; descriptions ≤35 chars each and must be provided as a pair. Returns changeId, assetId, and link resource names. To attach an existing sitelink to more targets later, call linkAsset.
| Name | Required | Description | Default |
|---|---|---|---|
| targets | No | Optional serving targets. Omit or pass [] to create the asset only. Array of targets. Each target is an OBJECT with a `level` discriminator — bare resource strings (e.g. "customers/123") are rejected. Shapes: { level: 'customer' } (account-wide), { level: 'campaign', campaignId: '123' }, { level: 'ad_group', adGroupId: '456' }, { level: 'asset_group', assetGroupId: '789' } (Performance Max only). Example: [{ level: 'customer' }, { level: 'campaign', campaignId: '12345' }]. | |
| finalUrl | Yes | Destination URL for the sitelink | |
| linkText | Yes | Sitelink text (≤25 chars), e.g. 'Pricing' | |
| accountId | No | Account ID (omit for primary) | |
| description1 | No | Optional sitelink description line 1 (≤35 chars). If provided, description2 is also required. | |
| description2 | No | Optional sitelink description line 2 (≤35 chars). If provided, description1 is also required. | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
createStructuredSnippetAssetIdempotentInspect
Create a structured snippet asset (header + 3-10 values, each ≤25 chars). Optionally link it to customer/campaign/ad-group targets via targets. Valid headers: Brands, Amenities, Styles, Types, Destinations, Services, Courses, Neighborhoods, Shows, Insurance coverage, Degree programs, Featured Hotels, Models. Alias accepted: "Service catalog" → "Services". Returns changeId, assetId, and link resource names. To attach an existing snippet to more targets later, call linkAsset.
| Name | Required | Description | Default |
|---|---|---|---|
| header | Yes | Structured snippet header. Must be one of: Brands, Amenities, Styles, Types, Destinations, Services, Courses, Neighborhoods, Shows, Insurance coverage, Degree programs, Featured Hotels, Models. "Service catalog" is accepted and normalized to "Services". | |
| values | Yes | Snippet values, 3-10 items, each ≤25 chars | |
| targets | No | Optional serving targets. Omit or pass [] to create the asset only. Array of targets. Each target is an OBJECT with a `level` discriminator — bare resource strings (e.g. "customers/123") are rejected. Shapes: { level: 'customer' } (account-wide), { level: 'campaign', campaignId: '123' }, { level: 'ad_group', adGroupId: '456' }, { level: 'asset_group', assetGroupId: '789' } (Performance Max only). Example: [{ level: 'customer' }, { level: 'campaign', campaignId: '12345' }]. | |
| accountId | No | Account ID (omit for primary) | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
createVideoCampaignIdempotentInspect
Create a YouTube TrueView in-stream video campaign. Requires an existing YouTube video ID. Starts PAUSED. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| adName | No | Ad name. Defaults to '{campaignName} - Video Ad'. | |
| bidding | No | Bidding configuration. Defaults to TARGET_CPV. | |
| finalUrl | Yes | Primary landing page URL. | |
| headline | Yes | Short headline, max 30 chars. | |
| accountId | No | Account ID (omit for primary) | |
| description | No | Ad description, max 90 chars. | |
| languageIds | No | Language constant IDs. Defaults to no restriction. | |
| callToAction | No | Call-to-action text (e.g. 'LEARN_MORE', 'SHOP_NOW'). Omit to use Google's default. | |
| campaignName | Yes | ||
| geoTargetIds | No | Geo target constant IDs (e.g. '2840' for US). Use searchGeoTargets to find IDs. | |
| longHeadline | No | Long headline, max 90 chars. | |
| youtubeVideoId | Yes | YouTube video ID (e.g. 'abc123XYZ' from youtube.com/watch?v=abc123XYZ). Must be uploaded to YouTube. | |
| dailyBudgetDollars | Yes | Daily budget in dollars |
enableAdIdempotentInspect
Re-enable a paused ad. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| adId | Yes | ||
| accountId | No | Account ID (omit for primary) | |
| adGroupId | Yes | ||
| campaignId | Yes | Campaign ID (for logging) | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
enableCampaignIdempotentInspect
Re-enable a paused campaign. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| campaignId | Yes | ||
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
enableKeywordIdempotentInspect
Re-enable a paused keyword. Only needs adGroupId + criterionId (no campaignId, unlike pauseKeyword). Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| adGroupId | Yes | ||
| criterionId | Yes | Keyword criterion ID (query keyword_view via runScript) |
enablePmaxAssetGroupIdempotentInspect
Re-enable a paused Performance Max asset group so it can serve ads again. Use getPmaxAssetGroups to find asset group IDs. Returns a changeId for undo support.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| campaignId | Yes | Performance Max campaign ID | |
| assetGroupId | Yes | Asset group ID to enable (query asset_group WHERE type = PERFORMANCE_MAX via runScript) | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
endExperimentDestructiveIdempotentInspect
Stop a running experiment immediately, without waiting for the scheduled end date. The trial campaign keeps its current state but stops splitting traffic. Use when the test has produced enough data and you DON'T want to apply the changes back to the base campaign. Status precondition: experiment must be ENABLED, INITIATED, or HALTED. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| experimentResourceName | Yes |
evaluateChangeInterventionRead-onlyInspect
Run the server-side observational evaluation for one Impact Monitor intervention. Compares the campaign's 7-day before window vs the post-change window, counts same-campaign confounders, stores an evaluation row, and returns a conservative verdict.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| afterWindowDays | No | ||
| baselineWindowDays | No | ||
| changeInterventionId | Yes |
fileInternalNotFairToolFeedbackInspect
Internal NotFair tool-feedback channel. Privately report MCP/tool friction that got in the way of helping the user — unclear descriptions, missing capabilities, clunky workflows, confusing errors, or duplicate tools.
This is not customer support, not user feedback, and not a success/quality rating. It is an internal NotFair engineering signal. When tool design gets in the way of a real user task, file one concrete report here so we can fix the tool surface. The user benefits in their next session; every other agent serving every other user benefits too.
AUTO-SURFACE THIS WHEN:
A tool description was unclear and you weren't sure how to use it.
You wanted to accomplish something for the user but no tool existed for it.
A workflow took many tool calls when one bulk operation could have replaced them.
An error message returned by a tool didn't help you debug or recover.
Two tools have overlapping purposes and the choice was confusing.
TIMING — file NOW, not later. This is the most important rule here:
File at the moment of friction, BEFORE you implement the workaround. Once you've routed around the issue, you will forget — that's the dominant failure mode for this tool.
If you've told the user "I'll file feedback" or anything similar, call this BEFORE your next user-facing message. No "I'll get to it after" — file first, then continue the task.
Filing is one tool call. It does not need the user's permission and does not need to be announced to them. Just file it.
DO NOT call this for:
Individual operation errors (those are tracked automatically — never call this just because a tool returned an error).
Confirming that a task succeeded.
Rating your own output quality.
Anything the user explicitly asked you to escalate (use the in-app feedback form for that).
Be specific. Reference tools by name and propose a concrete change. Submissions go directly to the NotFair team; the user does not see this channel.
Volume: file freely up to 5 per session. Quality of each report matters far more than parsimony — one specific, well-grounded report beats three vague ones, but underreporting is the bigger risk than overreporting.
| Name | Required | Description | Default |
|---|---|---|---|
| category | Yes | Type of feedback. description_unclear=tool docs were ambiguous; missing_capability=no tool for the intent; ergonomic=workflow took too many calls; error_message_unclear=error didn't help debugging; workflow_gap=tools couldn't be composed for the goal; duplicate_tools=two tools confusingly overlap; other=anything else. | |
| user_goal | No | What the user was trying to accomplish — gives the team the use case context. Avoid PII. | |
| suggestion | Yes | Concrete change you'd recommend. | |
| observation | Yes | What was confusing, painful, or missing. Be specific — quote what tripped you up. | |
| affected_tool | Yes | Tool name (e.g. 'pauseKeyword'), or 'general' if cross-cutting. |
generate_imageInspect
Generate one image from a prompt using OpenAI GPT Image 2. Returns a public URL you can embed in markdown or pass to a creative-asset tool (e.g. Google Ads createImageAsset). Counts against the user's monthly quota.
Prompt craft (GPT Image 2 rewards long, specific, instruction-style prompts — write a paragraph, not keywords):
Lead with the medium: photograph, 3D render, isometric vector, watercolor, flat illustration, studio product shot. Single biggest quality lever.
Then specify subject, setting, mood, color palette, lighting (e.g. 'golden hour, soft backlight'), and camera/perspective (close-up, wide, overhead, low angle, macro).
Keep the focal subject in the center 80% of the frame — ad platforms crop edges across placements.
Prefer lifestyle / in-context scenes over isolated-on-white product shots. Google explicitly recommends 'physical settings with organic shadows and lighting' for ad creative.
Don't render text unless the user asks for specific copy. Overlaid text is often unreadable at small ad sizes and Google flags it as a quality issue.
Avoid negative prompts ('no X, no Y'). GPT Image often pulls the rejected concept in — describe what you want instead.
Ad-policy rules to bake into prompts:
No collages, borders, watermarks, mirrored / skewed / over-filtered looks.
No fake UI elements (play buttons, download/close icons) — Google Ads policy violation.
Don't overlay a logo on the photo; logos belong inside the scene (on a product, sign, storefront).
Blank space should be under 80% of the frame — the subject is the focus.
Aspect ratios — match the target placement:
Google Ads asset slots: '1.91:1' landscape (required), '1:1' square (required), '4:5' portrait, '9:16' vertical (Demand Gen / Shorts).
Meta / social: '1:1' or '4:5' feed; '9:16' stories/reels; '1.91:1' link previews.
Hero / web banners: '16:9' or '3:2'. Default is '1:1'.
Quality vs latency: 'low' ~5s drafts; 'medium' balanced; 'high' runs the four-stage Understand/Plan/Generate/Review pipeline (30–50× slower than low) — use only for production-final fidelity.
Output format: default 'png' (lossless). Use 'webp' or 'jpeg' for smaller photographic assets. background='transparent' requires png/webp (use for logos, cutouts, UI assets).
| Name | Required | Description | Default |
|---|---|---|---|
| model | No | OpenAI image model override. Defaults to gpt-image-2. Use 'gpt-image-1.5' or 'gpt-image-1-mini' to opt into older/cheaper models. | |
| prompt | Yes | Image prompt. Describe medium, subject, setting, mood, lighting, color palette, and camera/perspective in natural language — paragraph form outperforms keyword lists. See the tool description for ad-platform rules. Up to 32K chars. | |
| quality | No | Generation quality. Defaults to 'auto' (OpenAI picks). 'low' is fastest (~5s), 'medium' is balanced, 'high' runs the four-stage Understand/Plan/Generate/Review pipeline (30–50× slower than low) and produces the most refined output. | |
| background | No | Background handling. 'transparent' requires outputFormat='png' or 'webp' (use for logos, cutouts, UI assets). 'opaque' forces a solid background. 'auto' (default) lets the model decide. | |
| aspectRatio | No | Aspect ratio. Common values: '1:1' (square), '16:9' (landscape), '9:16' (portrait/story), '4:5' (feed post). Defaults to '1:1'. Mapped to a 16-aligned WxH size server-side. | |
| outputFormat | No | Image file format. Defaults to 'png'. Use 'webp' or 'jpeg' for smaller photographic assets. |
getAssetLinksRead-onlyInspect
List every link for an asset across all 4 levels (customer / campaign / ad_group / asset_group). Use this before unlinkAssetLinks to discover which link resource_names to pass. Pure read — does not mutate. Returns an array of { level, linkResourceName, fieldType, target }.
| Name | Required | Description | Default |
|---|---|---|---|
| assetId | Yes | Asset ID (query `asset` via runScript, or pass an assetId you already have) | |
| accountId | No | Account ID (omit for primary) |
getChangeInterventionRead-onlyInspect
Get one Impact Monitor intervention with its linked operations and latest evaluation.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| changeInterventionId | Yes |
getChangesRead-onlyInspect
Recent changes made to the account via NotFair. Each change has a changeId usable with undoChange. Also returns derived changeGroups that group atomic write rows into likely user-intent episodes by requestId/scope/time so agents don't misread bulk edits as isolated one-offs. Reads NotFair's internal change log (Postgres), not Google's change_event API — for Google-side edits use runScript with SELECT ... FROM change_event.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| accountId | No | Account ID (omit for primary) | |
| campaignId | No |
getGuardrailsRead-onlyInspect
Get current guardrail limits. Returns campaign-specific guardrails if set, otherwise account-level defaults. Shows target CPA, monthly cap, and max change percentages for bids, budgets, and keyword pauses.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| campaignId | No | Campaign ID to check campaign-specific guardrails |
getKeywordIdeasRead-onlyInspect
Get keyword ideas with real search volume, competition, and CPC data from Google Ads Keyword Planner. Provide seed keywords and/or a URL to discover new keyword opportunities. Returns avg monthly searches, competition level, average CPC, and top-of-page bid estimates. No Google Ads account connection required — works for all users. Use searchGeoTargets first to find geo target IDs for location targeting. Keyword Planner is a separate API (not GAQL) — use this tool, not runScript.
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | Page URL to generate ideas from (combines with keywords if both provided) | |
| keywords | Yes | Seed keywords to generate ideas from | |
| language | No | Language constant ID (default: 1000 for English). Example: 1000=English, 1003=Spanish, 1001=French | |
| pageSize | No | Number of keyword ideas to return (max 50) | |
| geoTargetIds | No | Geo target constant IDs for location targeting (e.g. ['2840'] for US). Use searchGeoTargets to find IDs. |
getResourceMetadataRead-onlyInspect
Discover available fields for a GAQL resource. Returns selectable, filterable, and sortable fields with data types. Call this before writing a runScript that queries an unfamiliar resource, so you use valid field names. Example: getResourceMetadata('campaign') returns all campaign.* fields.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| resourceName | Yes | The GAQL resource name (e.g. 'campaign', 'ad_group', 'keyword_view', 'search_term_view') |
get_usageRead-onlyInspect
Show the current monthly image generation quota and usage for this account.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
graduateExperimentIdempotentInspect
Permanently fork the trial campaign into a standalone campaign that runs alongside the base. The agent only needs to supply the new budget — the trial campaign resource is resolved automatically. Use when both control and treatment are valuable and you want to keep them both running independently. Status precondition: experiment must be ENABLED. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| experimentResourceName | Yes | ||
| campaignBudgetResourceName | Yes | Full resource name of the budget the standalone graduated campaign should use, e.g. 'customers/123/campaignBudgets/789'. |
linkAssetIdempotentInspect
Link an existing asset to one or more serving targets in a single atomic mutate. Bulk-by-default: pass a single-element targets array for one target, or many for fan-out. Field types: CALLOUT, STRUCTURED_SNIPPET, SITELINK, CALL, MARKETING_IMAGE, SQUARE_MARKETING_IMAGE, AD_IMAGE. Level support varies by field type: MARKETING_IMAGE / SQUARE_MARKETING_IMAGE support all 4 levels including asset_group (Performance Max); CALLOUT / SITELINK / STRUCTURED_SNIPPET / CALL support customer/campaign/ad_group only; AD_IMAGE (Search/Display 'image extension' on RSAs) supports campaign/ad_group only. The underlying asset is field-type-agnostic — the same IMAGE asset can be linked as MARKETING_IMAGE at one target and AD_IMAGE at another. Auto-generated assets (asset.source = AUTOMATICALLY_CREATED) are rejected before the mutate. To remove links, use unlinkAssetLinks with the link resource_names returned here. Returns changeId and link resource names.
| Name | Required | Description | Default |
|---|---|---|---|
| assetId | Yes | Asset ID (query `asset` via runScript, or pass the assetId returned from a create*Asset call) | |
| targets | Yes | One or more serving targets. Array of targets. Each target is an OBJECT with a `level` discriminator — bare resource strings (e.g. "customers/123") are rejected. Shapes: { level: 'customer' } (account-wide), { level: 'campaign', campaignId: '123' }, { level: 'ad_group', adGroupId: '456' }, { level: 'asset_group', assetGroupId: '789' } (Performance Max only). Example: [{ level: 'customer' }, { level: 'campaign', campaignId: '12345' }]. | |
| accountId | No | Account ID (omit for primary) | |
| fieldType | Yes | Asset field type — what kind of asset this is and which serving slot it goes in. | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
linkCampaignToBiddingStrategyIdempotentInspect
Link a campaign to a portfolio bidding strategy — the campaign will use the shared strategy's configuration. This replaces any standard (campaign-level) bidding config. Use listBiddingStrategies to find strategy IDs. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| campaignId | Yes | ||
| biddingStrategyId | Yes | ||
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
linkNegativeListToCampaignIdempotentInspect
Link a shared negative keyword list to a campaign. All keywords in the list will be blocked for this campaign. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| campaignId | Yes | ||
| sharedSetId | Yes | Shared set ID (query shared_set WHERE type = NEGATIVE_KEYWORDS via runScript) | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
listActiveExperimentsRead-onlyInspect
One-call, safety-oriented view of currently running Google Ads experiments. Returns ENABLED experiments only, with control/treatment campaign IDs and names, traffic split, dates, and recent campaign metrics for both arms. Use this before experiment analysis or before planning campaign mutations; do not stitch raw experiment + experiment_arm GAQL unless you need historical/removed experiments.
| Name | Required | Description | Default |
|---|---|---|---|
| days | No | Recent metrics window in days. Default 14, max 90. | |
| accountId | No | Account ID (omit for primary) |
listChangeInterventionsRead-onlyInspect
List Impact Monitor interventions grouped at the campaign episode level. Returns campaign-scoped write bundles with status, summary, requestIds, operation counts, and the latest evaluation if one exists.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| offset | No | ||
| status | No | ||
| accountId | No | Account ID (omit for primary) | |
| campaignId | No |
listConnectedAccountsRead-onlyInspect
List Google Ads accounts connected to this session. Returns accountIds for use with all other tools.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
listExperimentAsyncErrorsRead-onlyInspect
Read errors logged during the most recent scheduleExperiment or promoteExperiment long-running operation. An empty list means the LRO succeeded. A non-empty list means forking or promotion failed — usually a campaign-config issue (invalid budget, conflicting bidding strategy, missing conversion action). Call this after every scheduleExperiment / promoteExperiment.
| Name | Required | Description | Default |
|---|---|---|---|
| pageSize | No | ||
| accountId | No | Account ID (omit for primary) | |
| pageToken | No | ||
| experimentResourceName | Yes |
listKeywordsRead-onlyInspect
Typed keyword inventory for safe mutation prep. Use this when you need keyword criterion IDs for bulkPauseKeywords, bulkUpdateBids, moveKeywords, or to inspect current positive/negative keyword state. This is intentionally narrow: for performance analysis, date-ranged metrics, search terms, or custom joins, use runScript. Defaults are safety-oriented: positive keywords only, enabled criteria only, and rows under REMOVED campaigns/ad groups excluded.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum keywords to return. Default 500, max 1000. | |
| positive | No | true = positive keywords only (default). false = negative keywords only. | |
| accountId | No | Account ID (omit for primary) | |
| adGroupId | No | Optional ad group ID to narrow the inventory. | |
| campaignId | No | Optional campaign ID to narrow the inventory. | |
| enabledOnly | No | true = only ENABLED keyword criteria (default). false = include PAUSED, still excluding REMOVED criteria. | |
| includeBidInfo | No | Include CPC bid fields. | |
| includeQualityInfo | No | Include quality score sub-fields. | |
| excludeRemovedParents | No | Exclude keywords whose campaign or ad group is REMOVED. Default true. |
listQueryableResourcesRead-onlyInspect
List all queryable GAQL resources (e.g. campaign, ad_group, keyword_view). Pair with getResourceMetadata to discover fields, then write a runScript against them.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) |
moveKeywordsDestructiveIdempotentInspect
Move keywords between ad groups in the same campaign. Inherits match type from source keywords by default — specify matchType only to override. Allows partial success: successfully-added keywords are paused in source, failed ones are left untouched. Returns changeIds for both adds and pauses.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| matchType | No | Override match type in destination — omit to inherit from source | |
| campaignId | Yes | ||
| toAdGroupId | Yes | ||
| criterionIds | Yes | Keyword criterion IDs (query keyword_view via runScript) | |
| fromAdGroupId | Yes | ||
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
pauseAdIdempotentInspect
Pause an active ad. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| adId | Yes | ||
| accountId | No | Account ID (omit for primary) | |
| adGroupId | Yes | ||
| campaignId | Yes | Campaign ID (for logging) | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
pauseCampaignDestructiveIdempotentInspect
Pause a campaign, stopping all its ads. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| campaignId | Yes | ||
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
pauseKeywordIdempotentInspect
Pause a POSITIVE (active) keyword. Does NOT work on negative keywords — Google Ads has no 'pause' for negatives; call removeNegativeKeyword instead (and addNegativeKeyword to re-add later). Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| adGroupId | Yes | ||
| campaignId | Yes | ||
| criterionId | Yes | Keyword criterion ID (query keyword_view via runScript) | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
pausePmaxAssetGroupIdempotentInspect
Pause a Performance Max asset group. When paused, Google stops serving ads from this asset group while the campaign and other asset groups remain active. Use getPmaxAssetGroups to find asset group IDs. Returns a changeId for undo support.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| campaignId | Yes | Performance Max campaign ID | |
| assetGroupId | Yes | Asset group ID to pause (query asset_group WHERE type = PERFORMANCE_MAX via runScript) | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
promoteExperimentIdempotentInspect
Apply the treatment arm's changes back onto the base campaign and stop the trial. Long-running — like scheduleExperiment, returns immediately and you must follow up with listExperimentAsyncErrors. Use when the treatment is a clear winner and you want the base campaign to inherit the changes. Status precondition: experiment must be ENABLED. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| experimentResourceName | Yes |
removeAdIdempotentInspect
Permanently remove an ad from an ad group. This cannot be undone. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| adId | Yes | ||
| accountId | No | Account ID (omit for primary) | |
| adGroupId | Yes | ||
| campaignId | Yes | Campaign ID (for logging) | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
removeBiddingStrategyDestructiveIdempotentInspect
Remove a portfolio bidding strategy. All campaigns currently linked to it must be unlinked first (Google Ads will reject otherwise). Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| biddingStrategyId | Yes |
removeCampaignDestructiveIdempotentInspect
PERMANENTLY remove a campaign — cannot be undone, not even with undoChange. The campaign and all its ad groups, ads, and keywords will be deleted. Prefer pauseCampaign in most cases. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| campaignId | Yes | ||
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
removeConversionActionIdempotentInspect
Permanently delete a conversion action. Not undoable. Use this instead of updateConversionAction with status=REMOVED — Google rejects that with request_error=18. Conversion actions imported from GA4/UA/Floodlight/Firebase/Salesforce/Search Ads 360, Smart Campaign auto-actions, Store Visits, app-store actions, local_services_* / Local Services Ads actions, and manager-inherited actions are read-only via the API — the remove call will be rejected locally before reaching Google. To check before calling: read conversion_action.type and conversion_action.owner_customer via runScript (e.g. await ads.gaql(ads.queries.conversionActions)) or write a direct FROM conversion_action query. Modify read-only actions in the Google Ads UI or in the source system (GA4, Firebase, Salesforce, Floodlight). Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| conversionActionId | Yes | Conversion action ID to permanently delete |
removeKeywordFromNegativeListDestructiveIdempotentInspect
Remove a keyword from a shared negative keyword list. If the same keyword text exists under multiple match types, specify matchType to remove the correct one. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| keyword | Yes | Exact keyword text to remove | |
| accountId | No | Account ID (omit for primary) | |
| matchType | No | Match type to disambiguate | |
| sharedSetId | Yes | Shared set ID (query shared_set WHERE type = NEGATIVE_KEYWORDS via runScript) |
removeNegativeKeywordDestructiveIdempotentInspect
Remove a negative keyword from a campaign. This is the correct tool for 'pausing' or 'disabling' a negative keyword — Google Ads has no pause state for negatives, removing is the equivalent. To re-add later, call addNegativeKeyword with the same text and match type. If the same keyword text exists under multiple match types, specify matchType to remove the correct one. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| keyword | Yes | Exact negative keyword text to remove | |
| accountId | No | Account ID (omit for primary) | |
| matchType | No | Match type to disambiguate if the same text exists under multiple match types | |
| campaignId | Yes | ||
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
removeNegativeKeywordListDestructiveIdempotentInspect
Delete a shared negative keyword list. This also unlinks it from all campaigns. Permanent — cannot be undone. Use listNegativeKeywordLists to find the sharedSetId. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| sharedSetId | Yes | Shared set ID (query shared_set WHERE type = NEGATIVE_KEYWORDS via runScript) |
renameAdGroupIdempotentInspect
Rename an ad group. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| newName | Yes | ||
| accountId | No | Account ID (omit for primary) | |
| adGroupId | Yes | ||
| campaignId | Yes | ||
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
renameCampaignIdempotentInspect
Rename a campaign. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| newName | Yes | ||
| accountId | No | Account ID (omit for primary) | |
| campaignId | Yes | ||
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
reviewChangeImpactRead-onlyInspect
Estimate correlational impact of every successful change in the last days using daily campaign snapshots (captured by cron). For each change: compares 7-day daily averages BEFORE vs AFTER the change date on the affected campaign, classifies direction (improved/worsened/neutral/unknown), and returns cost/conversion/CPA deltas plus otherChangesInWindow so you can spot confounders (other writes in the 14-day envelope). Response includes per-action counts and a campaign-deduped aggregate sum — use this instead of stitching getChanges + a runScript performance query by hand. Ideal for weekly or ad-hoc impact reviews. Caveats: impact is correlational (seasonality, competitor bids, Google's algorithm also move numbers); changes <3 days old are typically 'tooNew' because the snapshot cron lags a day; keyword/ad changes attribute to the containing campaign (campaign-level granularity only); window boundaries are UTC.
| Name | Required | Description | Default |
|---|---|---|---|
| days | No | Lookback window in days. Default 7 (weekly review); max 90. | |
| limit | No | Max changes to attribute. Default 50; max 200. | |
| accountId | No | Account ID (omit for primary) |
runScriptRead-onlyInspect
Run a JavaScript orchestration script in a sandboxed QuickJS runtime. This is a REPLACEMENT for chaining individual tool calls, not a supplement — one runScript call does what would otherwise take 10+ sequential tool invocations.
── READ-ONLY (analytics and reporting only) ──
runScript is a READ-ONLY analytics sandbox. ads.gaql() and ads.gaqlParallel() only execute SELECT GAQL queries — they cannot pause, update, create, or delete anything. To mutate the account (pause keywords, update bids, create campaigns, add negatives, etc.), call the dedicated mutation tools (pauseKeyword, updateBid, bulkPauseKeywords, pauseCampaign, createCampaign, addNegativeKeyword, etc.) directly. Never try to perform mutations inside a runScript call.
── WHEN TO USE THIS ──
This is the DEFAULT tool for any open-ended analytical question about a Google Ads account. Reach for it first when you see:
"How is my account doing?" / "What's working?" / "What's broken?" / "How did last week go?"
"Audit my account" / "Find wasted spend" / "What should I change?" / "Any quick wins?"
Any question where you would otherwise fire 3+ read tools back-to-back
Any question that benefits from correlating surfaces (spend + search terms + quality scores + change events) in a single pass
runScript owns EVERY read of Google Ads data. There are no point-query read tools anymore — if the caller asks for spend, CPA, search terms, keywords, ads, impression share, or anything else expressible in GAQL, you write a runScript that queries it. The only non-runScript reads are for specialized services that aren't GAQL-expressible: searchGeoTargets, getChanges (NotFair's own change log), reviewChangeImpact, getKeywordIdeas. For schema discovery before a query, use getResourceMetadata and listQueryableResources.
── BATCHING DISCIPLINE (read this first) ──
Prefer ONE runScript call that fans out with ads.gaqlParallel (up to 20 queries concurrently) and does the full analysis in-script. Each runScript invocation costs ~5–10s of model deliberation PLUS the max GAQL latency across its queries. Batching 15 queries in one call ≈ 1 round-trip; doing the same across 5 calls ≈ 5 round-trips (5x slower).
Rules of thumb:
Cast a wide net on the first call. You have 20 parallel slots — use them even if you're not sure yet what you'll need. Filtering in-script is free.
Do NOT make follow-up runScript calls just to pull one more surface you should have included. If you catch yourself about to call runScript a second time, ask: "could I have put this in the first batch?" (almost always yes).
Return the finished analysis (rankings, top offenders, aggregates), not raw GaqlReport.rows arrays. The caller reads your return value into context — summarize first.
── API SURFACE (all on the ads namespace) ──
Async RPCs:
ads.gaql(query, limit?, options?) -> GaqlReport — single GAQL query. THIS IS THE ENTRY POINT FOR AD-HOC QUERIES. For one-off data pulls, use
return await ads.gaql('SELECT ...')— there is no separate runGaqlQuery tool.ads.gaqlParallel([{name, query, limit?}, ...], options?) -> { [name]: GaqlReport } — max 20 per call. USE THIS for multi-surface analysis. Fails the whole call if any subquery errors; pass
{ partial: true }only when you explicitly want{ error }entries mixed with successful reports.options.excludeRemovedParents defaults to true. Rows under REMOVED campaigns/ad groups are filtered out server-side because most audits need current serving state. Pass
{ excludeRemovedParents: false }only for historical analysis.
Canonical gaqlParallel shape:
const r = await ads.gaqlParallel([
{ name: "campaigns", query: SELECT campaign.id, campaign.name, metrics.cost_micros FROM campaign WHERE segments.date DURING LAST_30_DAYS, limit: 50 },
{ name: "searchTerms", query: SELECT search_term_view.search_term, metrics.clicks, metrics.conversions FROM search_term_view WHERE segments.date DURING LAST_30_DAYS, limit: 100 },
]);
const campaigns = r.campaigns.rows ?? [];
For intentional partial success:
const r = await ads.gaqlParallel([...], { partial: true }); const rows = "error" in r.searchTerms ? [] : r.searchTerms.rows;
Pre-built GAQL strings (sync, no RPC cost):
Parameterless: ads.queries.accountInfo | geoTargeting | qualityScores | adGroups | conversionActions | recommendations | billingSetups | audienceSegmentCheck | negativeKeywords | campaignAssets | adGroupAssets | sharedNegativeKeywordLists | sharedNegativeKeywordMembers | pausedCampaigns | customerManagerLinks
Date-windowed builders (call with YYYY-MM-DD): ads.queries.campaigns(start,end) | keywords | searchTerms | convertingSearchTerms | zeroConversionKeywords | ads | devicePerformance | networkSegmentation | landingPages | changeEvents | dailyCampaignMetrics | conversionActionPerformance
Canonical audit pack: ads.queries.auditPack(start,end) -> 23 named queries covering setup, campaigns, keywords, search terms, ads/assets, negatives, conversion actions/performance, recommendations, billing setup, paused campaigns, manager links, and recent Google-side change events. Prefer this for account audits instead of hand-selecting a narrow subset.
Sync helpers: ads.helpers.getDateRange(days), formatDate, micros, toMicros, normalizeCustomerId, daysBetween, extractChangedFields, generateBrandVariants Constants: ads.constants.RESOURCE_CHANGE_OP, CHANGE_RESOURCE_TYPE, CHANGE_CLIENT_TYPE (numeric enum → label maps)
── HUMANIZED RESPONSES + REPORT METADATA ──
Every GaqlReport includes meta: asOf, resource, dateRange/days, currencyCode/timeZone when selected, reportingLagDays, row limits/truncation, removed-parent behavior, campaign/ad-group status filters, campaign type filters, and data-completeness warnings. Read meta before making freshness/exhaustiveness claims.
Rows are augmented post-fetch so you can read the LLM-friendly form directly:
Enum integer fields get a sibling
<field>_name(canonical Google Ads enum name). Readbidding_strategy_type_name === "MAXIMIZE_CONVERSIONS", not the integer 10. Avoids the BiddingStrategyType landmines (10=MAX_CONVERSIONS, 11=MAX_CONVERSION_VALUE, 9=TARGET_SPEND/MaxClicks, 15=TARGET_IMPRESSION_SHARE).Money fields ending
_microsget a sibling<base>_valuein major units (cost_micros: 11_000_000⇒cost_value: 11). Currency-agnostic — works for USD/EUR/JPY. Raw_microsis preserved. ⚠ IMPORTANT:_name/_valuesiblings are NOT GAQL fields — do NOT put them in SELECT or WHERE. They appear automatically in result rows when the corresponding raw field is selected (_name→ base enum field;_value→ the_microsfield).
── DATE LITERALS (GAQL only supports a fixed set) ──
Valid DURING literals: TODAY, YESTERDAY, LAST_7_DAYS, LAST_14_DAYS, LAST_30_DAYS, THIS_MONTH, LAST_MONTH, LAST_BUSINESS_WEEK, LAST_WEEK_MON_SUN, LAST_WEEK_SUN_SAT, THIS_WEEK_MON_TODAY, THIS_WEEK_SUN_TODAY. There is no LAST_60_DAYS, LAST_90_DAYS, LAST_180_DAYS, THIS_YEAR, or LAST_YEAR. For windows >30 days, use a custom range:
const { start, end } = ads.helpers.getDateRange(90);
const q = SELECT campaign.id, metrics.cost_micros FROM campaign WHERE segments.date BETWEEN '${start}' AND '${end}';
(As a backstop, the server auto-rewrites unsupported DURING LAST_N_DAYS/THIS_YEAR/LAST_YEAR to BETWEEN, but writing it correctly is faster and clearer.)
Note: change_event only supports the last 30 days regardless of how you express the range.
── COMMON GOTCHAS (the validator will reject these before they reach Google) ──
change_event REQUIRES
change_event.change_date_timein WHERE.segments.date DURING ...does NOT work for this resource (Google rejects with change_event_error=3). Window cap is 30 rolling days. Easiest:ads.queries.changeEvents(start, end)builds the right shape.GAQL has no SQL JOIN. Select compatible related-resource fields directly from one FROM resource (
campaign_budget.amount_microscan be selected fromFROM campaign), or run two queries and join rows in JavaScript.Enums in WHERE are STRING names, not numbers. Write
WHERE campaign.status = 'PAUSED', never= 3. Same forad_group.status,ad_group_ad.status,ad_group_criterion.status,conversion_action.status,asset_group.status. Valid status values: ENABLED, PAUSED, REMOVED. For other enums (advertising_channel_type, bidding_strategy_type, etc.), callgetResourceMetadatawith the query's FROM resource, e.g.getResourceMetadata('campaign').Manager-link status has no REMOVED enum. For
customer_manager_link.status, use ACTIVE, INACTIVE, PENDING, REFUSED, or CANCELED; omit the filter if you only want all rows.metrics.*is NOT selectable fromFROM conversion_action. That resource carries dimensional fields only (name, type, status, counting). To break down metric counts by conversion action: queryFROM campaign(orad_group) and SELECTsegments.conversion_action_name. To list configured actions: drop the metrics and keep onlyconversion_action.*fields.Local Services conversion actions are often segment-only. LSA /
local_services_*conversion names can appear insegments.conversion_action_namebut not as mutable rows inFROM conversion_action. Before callingupdateConversionAction/removeConversionAction, checkconversion_action.typeandconversion_action.owner_customer(e.g. viaads.gaql(ads.queries.conversionActions)); if the type is GA4/UA/Floodlight/Firebase/Salesforce/SA360 imports, Smart Campaign auto-actions, Store Visits, app-store actions, or the owner_customer points at a different customer (manager-inherited), treat as Google-managed/read-only.segments.conversion_action_nameand friends don't pair withmetrics.cost_micros. Google reports cost at the campaign/ad_group level, not per conversion action — pick one or the other (query_error=53). For per-action cost-per-conversion, dividecost_micros(campaign-total) by per-actionmetrics.conversionsin-script.Fields used in WHERE must also be in SELECT (query_error=16). The server auto-injects
campaign.status/ad_group.statusfor REMOVED-parent filters and promotes non-datesegments.*predicate fields into SELECT automatically. Date segments are left unselected to avoid changing row granularity.segments.date BETWEENtakes explicit ISO dates only. Do not writeBETWEEN 'LAST_30_DAYS' AND 'undefined'; usesegments.date DURING LAST_30_DAYS, or useads.helpers.getDateRange(days)and interpolateYYYY-MM-DDdates.search_term_viewrequires a finitesegments.datefilter. Includesegments.date DURING LAST_30_DAYSor aBETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'clause.keyword_viewincludes ad-group-level NEGATIVES. Filterad_group_criterion.negative = FALSEfor positives only — and addad_group_criterion.negativeto your SELECT (predicate-fields-must-be-in-SELECT applies). Negatives have 0 impressions/clicks/cost/conversions by definition (they block serving), so anymetrics.* = 0filter without this predicate sweeps up every negative in the account.Keyword quality fields are split by resource. Query delivery metrics (
metrics.clicks,metrics.cost_micros, conversions, etc.) fromFROM keyword_view. Query quality-score fields fromFROM ad_group_criterionwithout metrics:ad_group_criterion.quality_info.quality_score,creative_quality_score,post_click_quality_score, andsearch_predicted_ctr. There is nometrics.quality_info.quality_score,ad_group_criterion.quality_info.ad_relevance, orad_group_criterion.quality_info.landing_page_experience.Known hallucinated fields: there is no
metrics.average_cpc_micros,metrics.cost_per_conversion_micros,metrics.impression_share,metrics.search_lost_is_rank,metrics.search_lost_is_budget,metrics.conversion_rate,metrics.quality_info.quality_score,asset.status,asset_group_asset.performance_label,asset.sitelink_asset.final_urls,campaign.url_expansion_opt_out,campaign.budget_micros,campaign.budget_amount_micros,campaign_criterion.proximity.address.city,campaign_criterion.audience.audience,change_event.campaign.name,change_event.resource_type,ad_group_criterion.quality_info.ad_relevance,ad_group_criterion.quality_info.landing_page_experience,campaign_experiment.*,conversion_action.default_value,conversion_action.last_conversion_date,conversion_action.most_recent_conversion_date,recommendation.impact.base_metrics.*,recommendation.keyword_match_type,billing_setup.payments_account_info.*,auction_insight.domain, or bareresource_name. Usemetrics.average_cpc; usemetrics.cost_per_conversion; for Search campaigns usemetrics.search_impression_share,metrics.search_rank_lost_impression_share, andmetrics.search_budget_lost_impression_share; calculate conversion rate frommetrics.conversions / metrics.clicks; budget lives oncampaign_budget.amount_micros; asset serving status lives on the link resource (campaign_asset.status,ad_group_asset.status,asset_group_asset.status,customer_asset.status); usecampaign_criterion.proximity.address.city_name; usechange_event.change_resource_type; useconversion_action.value_settings.default_value; useads.queries.billingSetupsfor safe billing reads; replaceresource_namewith<resource>.resource_name; callgetResourceMetadata(<resource>)for the rest.
Rules: top-level await works; no fetch/require/process/fs; return value must be JSON-serializable; defaults are 30000ms (30s) timeout, max 45000ms (45s), 500KB return cap, 100K log chars.
── CANONICAL AUDIT (one call, wide net, filter in-script) ──
const { start, end } = ads.helpers.getDateRange(30); const r = await ads.gaqlParallel(ads.queries.auditPack(start, end)); // Inspect r.campaigns.meta / r.searchTerms.meta for freshness, filters, and truncation before concluding. const worstCampaigns = (r.campaigns.rows ?? []) .map(c => ({ name: c.campaign.name, spend: c.metrics.cost_micros / 1e6, cpa: (c.metrics.cost_micros / 1e6) / (c.metrics.conversions || 1), convRate: c.metrics.conversions / (c.metrics.clicks || 1), })) .sort((a, b) => b.cpa - a.cpa).slice(0, 5); const topZeroConvKws = (r.zeroConversionKeywords.rows ?? []).slice(0, 10).map(k => ({ text: k.ad_group_criterion.keyword.text, spend: k.metrics.cost_micros / 1e6, })); return { worstCampaigns, topZeroConvKws, /* ... aggregates only, not raw rows ... */ };
── ANTI-PATTERNS (don't) ──
Calling runScript 5+ times in sequence to fetch different surfaces — that's exactly what gaqlParallel replaces.
Using ads.gaql in a JS loop when the queries are independent — use gaqlParallel.
Returning entire GaqlReport.rows arrays — summarize, rank, or aggregate first.
Passing non-SELECT statements to ads.gaql() — GAQL is read-only, the call will throw immediately. Mutations go through dedicated tools, not runScript.
| Name | Required | Description | Default |
|---|---|---|---|
| code | Yes | JavaScript source. Top-level await allowed. See tool description for the API surface. | |
| accountId | No | Account ID (omit for primary) | |
| timeoutMs | No | Wall-clock cap in MILLISECONDS before the script is interrupted. Default 30000 (30s), max 45000 (45s). Examples: pass 45000 for a 45-second cap. Do NOT pass 45 — that's 45ms and will be rejected. Raise to 45000 when batching 15+ parallel queries via gaqlParallel. |
scheduleExperimentIdempotentInspect
Step 4 of 5. Kick off the experiment — Google forks the in-design (trial) campaign into a real serving campaign. Returns immediately with an operation name; forking happens asynchronously over a few seconds to a few minutes. ALWAYS follow up with listExperimentAsyncErrors to verify forking succeeded — async errors don't surface from this call. Status precondition: experiment must be SETUP. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| experimentResourceName | Yes | Resource name of the experiment to schedule. |
searchGeoTargetsRead-onlyInspect
Search for geo target locations by name (cities, counties, states, countries). Returns geo target constant IDs that can be used with updateCampaignSettings locationTargeting and negativeLocationTargeting. Example: search 'Kitsap County' to get its ID, then pass that ID to updateCampaignSettings to target or exclude it.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Location name to search for (e.g. 'Kitsap County', 'Seattle', 'Washington', 'United States') | |
| locale | No | Locale for results (default: 'en') | |
| accountId | No | Account ID (omit for primary) | |
| countryCode | No | ISO 3166-1 alpha-2 country code to narrow results (e.g. 'US', 'CA', 'GB') |
setGuardrailsIdempotentInspect
Set guardrail limits for bid changes, budget changes, and keyword pauses. Can be set at account level (omit campaignId) or per-campaign. These limits cap how much the AI can change in a single operation.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| targetCpa | No | Target CPA in dollars | |
| campaignId | No | Campaign ID for campaign-specific guardrails (omit for account-level defaults) | |
| monthlyCap | No | Monthly spend cap in dollars | |
| maxBidChangePct | No | Max bid change per adjustment as decimal (e.g. 0.25 = 25%) | |
| maxBudgetChangePct | No | Max budget change per adjustment as decimal (e.g. 0.50 = 50%) | |
| maxKeywordPausePct | No | Max fraction of keywords that can be paused at once (e.g. 0.30 = 30%) |
setTrackingTemplateIdempotentInspect
Set or clear the click-tracking URL suffix at the account, campaign, ad group, or ad level. Uses ValueTrack parameters. Pass empty string to clear. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| adId | No | The ad ID. Required when level is 'ad'. | |
| level | Yes | ||
| accountId | No | Account ID (omit for primary) | |
| adGroupId | No | The ad group ID. Required when level is 'ad_group'. | |
| campaignId | No | The campaign ID. Required when level is 'campaign'. | |
| trackingTemplate | Yes | Tracking URL template (e.g. '{lpurl}?utm_source=google&utm_medium=cpc'). Empty string to remove. | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
summarizeAccountSetupRead-onlyInspect
One-shot, human-readable snapshot of how the account is configured: currency + time zone, every non-removed campaign with its bidding strategy and tCPA/tROAS in major units, every conversion action with category + primary_for_goal flag, plus diagnostic notes when the setup is unusual (no primary conversion action, mixed optimization modes). Call this FIRST in any strategic conversation — it gives you the conversion hierarchy and bidding posture as named strings so you don't misread enum integers (the BiddingStrategyType landmines: 10=MAXIMIZE_CONVERSIONS, 11=MAXIMIZE_CONVERSION_VALUE, 9=TARGET_SPEND, 15=TARGET_IMPRESSION_SHARE) or treat micros as dollars. Replaces 3+ runScript calls (account info + campaigns + conversion actions) for the canonical setup question.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) |
undoChangeDestructiveInspect
Undo a previous write operation by changeId. Only works within 7 days AND only if the entity hasn't been modified since the original change. Returns error if either condition is not met.
| Name | Required | Description | Default |
|---|---|---|---|
| changeId | Yes | changeId returned by the original write operation | |
| accountId | No | Account ID (omit for primary) |
unlinkAssetLinksDestructiveIdempotentInspect
Remove one or more asset links by their canonical link resource_names (returned by getAssetLinks, linkAsset, or any create*Asset call). Bulk-by-default: pass a single-element array for one link, or many for atomic bulk removal. The underlying asset is NOT deleted — Google Ads assets are immutable. To 'delete' an asset, remove every link that references it; the asset row remains in the account but stops serving. Returns changeId(s).
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| linkResourceNames | Yes | Canonical link resource_names. Each must be a path containing /customerAssets/, /campaignAssets/, /adGroupAssets/, or /assetGroupAssets/. Get these from `getAssetLinks(assetId)` or from a previous link operation. | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
unlinkNegativeListFromCampaignDestructiveIdempotentInspect
Unlink a shared negative keyword list from a campaign. The list's keywords will no longer be blocked for this campaign. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| campaignId | Yes | ||
| sharedSetId | Yes | Shared set ID (query shared_set WHERE type = NEGATIVE_KEYWORDS via runScript) | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
updateAdAssetsDestructiveIdempotentInspect
Replace headlines and descriptions for a Responsive Search Ad. Headlines and descriptions are COMPLETE replacement — provide every asset, not just changed ones. Display URL paths (path1/path2) are partial: omit them and existing values are preserved; provide them to override. Optionally pin assets to fixed positions. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| adId | Yes | ||
| path1 | No | Display URL path 1 (max 15 chars, no spaces). Omit to preserve the existing path. | |
| path2 | No | Display URL path 2 (max 15 chars, no spaces). Requires path1. Omit to preserve the existing path. | |
| accountId | No | Account ID (omit for primary) | |
| adGroupId | Yes | ||
| headlines | Yes | Complete replacement headlines (3-15, max 30 chars each) | |
| campaignId | Yes | Campaign ID (for logging) | |
| descriptions | Yes | Complete replacement descriptions (2-4, max 90 chars each) | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
updateAdFinalUrlIdempotentInspect
Update the landing page URL for an ad. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| adId | Yes | ||
| finalUrl | Yes | ||
| accountId | No | Account ID (omit for primary) | |
| adGroupId | Yes | ||
| campaignId | Yes | Campaign ID (for logging) | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
updateAdGroupIdempotentInspect
Update an ad group's default max CPC bid, target CPA, status, and/or name in one call. Use cpcBidDollars to set the ad-group default bid (only effective on MANUAL_CPC / ENHANCED_CPC campaigns); use targetCpaDollars to override the campaign's target CPA at the ad-group level (only effective on TARGET_CPA / MAXIMIZE_CONVERSIONS campaigns — surfaces a warning otherwise, no error). Subject to the per-account maxBidChangePct guardrail (default 25%, raise with setGuardrails). The guardrail is bypassed only when there's no real ad-group-level bid yet — cpc_bid_micros is either null (inheriting the campaign default) or 0 (set-but-unset). Any positive existing bid — including Google's €0.01 (10,000 micros) placeholder on newly-created MANUAL_CPC ad groups — is treated as a real value and the cap applies. To ramp a freshly-launched ad group from the placeholder, call setGuardrails ({ maxBidChangePct: 1.0 }) first, do the bumps, then restore the guardrail. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| status | No | Set ad group status. | |
| newName | No | Rename the ad group. Use this OR renameAdGroup — equivalent behavior. | |
| accountId | No | Account ID (omit for primary) | |
| adGroupId | Yes | ||
| campaignId | Yes | Campaign ID (for logging and guardrail resolution) | |
| cpcBidDollars | No | New ad-group default max CPC in dollars (e.g. 1.50). Only honored for MANUAL_CPC/ENHANCED_CPC campaigns. | |
| targetCpaDollars | No | Override the campaign's target CPA at the ad-group level, in dollars (minimum 0.10). Only effective on TARGET_CPA / MAXIMIZE_CONVERSIONS campaigns. | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
updateBidIdempotentInspect
Update a keyword's CPC bid. Only works with MANUAL_CPC or ENHANCED_CPC bidding. Each call is capped by the per-account/per-campaign maxBidChangePct guardrail (default 25%); raise it with setGuardrails (max 100% per call) and confirm with the user before stepping bigger. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| adGroupId | Yes | ||
| campaignId | Yes | ||
| criterionId | Yes | Keyword criterion ID (query keyword_view via runScript) | |
| newBidDollars | Yes | New bid in dollars (e.g. 1.50) | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
updateBiddingStrategyIdempotentInspect
Edit a portfolio bidding strategy's name and/or target value. You can change targetCpa on TARGET_CPA/MAXIMIZE_CONVERSIONS strategies, and targetRoas on TARGET_ROAS/MAXIMIZE_CONVERSION_VALUE strategies. The strategy type itself cannot be changed. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | ||
| accountId | No | Account ID (omit for primary) | |
| targetCpa | No | New target CPA in dollars | |
| targetRoas | No | New target ROAS multiplier | |
| biddingStrategyId | Yes |
updateCampaignBiddingIdempotentInspect
Update a campaign's bidding strategy. Supports: TARGET_CPA (set a target cost per acquisition), MAXIMIZE_CONVERSIONS (optionally with a target CPA cap), MAXIMIZE_CONVERSION_VALUE (maximize total conversion value, optionally with a target ROAS — required for PMAX value-based bidding), TARGET_ROAS (target return on ad spend), MAXIMIZE_CLICKS (optionally with cpcBidCeiling), MANUAL_CPC, TARGET_IMPRESSION_SHARE (presence-based — 'just win' on a given SERP position, ideal for brand campaigns). For TARGET_CPA, targetCpa is required (in dollars). For MAXIMIZE_CONVERSIONS, targetCpa is optional (acts as a cap). For TARGET_ROAS and MAXIMIZE_CONVERSION_VALUE, targetRoas is required/optional respectively (e.g. 2.0 = 200% ROAS). For TARGET_IMPRESSION_SHARE, impressionShareLocation, locationFraction, and cpcBidCeiling are all required — Google will not accept this strategy without all three. For MAXIMIZE_CLICKS, cpcBidCeiling is optional (defaults to effectively uncapped). Passing cpcBidCeiling with any other strategy is rejected. Returns a changeId for undo support.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| targetCpa | No | Target CPA in dollars (e.g. 10.50 for $10.50). Required for TARGET_CPA, optional cap for MAXIMIZE_CONVERSIONS. | |
| campaignId | Yes | ||
| targetRoas | No | Target ROAS as a multiplier (e.g. 2.0 = 200% return). Required for TARGET_ROAS, optional cap for MAXIMIZE_CONVERSION_VALUE. | |
| cpcBidCeiling | No | Max CPC bid cap in dollars (e.g. 2.00 = $2.00). REQUIRED for TARGET_IMPRESSION_SHARE — without a ceiling Google can bid unbounded to hit the IS target. OPTIONAL for MAXIMIZE_CLICKS (default: effectively uncapped at $10,000) — use this to lower an oversized cap on Target Spend / Maximize Clicks campaigns. REJECTED for all other strategies; they have no campaign-level CPC ceiling. | |
| biddingStrategy | Yes | The bidding strategy to set. Use MAXIMIZE_CONVERSION_VALUE for Performance Max campaigns optimizing for revenue/value. Use TARGET_IMPRESSION_SHARE for brand campaigns where 'just win the auction' matters more than per-conversion efficiency. | |
| locationFraction | No | TARGET_IMPRESSION_SHARE only: the IS target as a fraction from 0.01 to 1.00 (e.g. 0.95 = 95%). Typical brand target is 0.90–0.95. | |
| impressionShareLocation | No | TARGET_IMPRESSION_SHARE only: where on the SERP to target. TOP_OF_PAGE = above organic results (most common for brand). ABSOLUTE_TOP_OF_PAGE = position 1. ANYWHERE_ON_PAGE = any paid slot. | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
updateCampaignBudgetIdempotentInspect
Update a campaign's daily budget. Capped at 50% change per adjustment, minimum $1/day. Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| campaignId | Yes | ||
| newDailyBudgetDollars | Yes | New daily budget in dollars (e.g. 25.00) | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
updateCampaignGoalsIdempotentInspect
Switch a campaign between campaign-specific and account-level conversion goals. Set to CUSTOMER to use account-level goals (required before switching to non-conversion bidding strategies like MAXIMIZE_CLICKS or MANUAL_CPC). Set to CAMPAIGN for campaign-specific goals. Note: updateCampaignBidding auto-handles this when switching to MAXIMIZE_CLICKS or MANUAL_CPC, so this tool is only needed for manual goal config changes.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| campaignId | Yes | ||
| goalConfigLevel | Yes | CUSTOMER = use account-level conversion goals. CAMPAIGN = use campaign-specific conversion goals. | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
updateCampaignLanguagesIdempotentInspect
Add or remove language targeting criteria on a campaign. Pass language constant IDs (e.g. '1000' for English, '1003' for Spanish). Returns a changeId per mutation.
| Name | Required | Description | Default |
|---|---|---|---|
| add | No | Language constant IDs to add (e.g. ['1000'] for English) | |
| remove | No | Language constant IDs to remove | |
| accountId | No | Account ID (omit for primary) | |
| campaignId | Yes | ||
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
updateCampaignSettingsIdempotentInspect
Update campaign network targeting, location targeting, and/or ad schedule. Networks: toggle Google Search, Search Partners, Display Network. Locations: add/remove geo targets (positive or negative) by geo target constant ID (e.g. '2840' for US, '200840' for Seattle-Tacoma DMA). Ad schedule: replace the entire schedule with a list of slots (use dayOfWeek 'ALL' as a shortcut for all 7 days; pass an empty array to clear the schedule and run 24/7). NOTE: If the campaign uses smart bidding (TARGET_CPA/TARGET_ROAS/MAXIMIZE_CONVERSIONS/MAXIMIZE_CONVERSION_VALUE), schedule restrictions are respected but can hurt performance by removing learning signal. Prefer 24/7 schedules unless you have strong evidence specific hours are unprofitable. Returns a changeId per mutation plus any warnings. Geo intent: set positiveGeoTargetType to PRESENCE (only people physically in the area) or PRESENCE_OR_INTEREST (default — also includes people searching for the area). Proximity: add radius-based targeting (5-mile circles) by lat/lng via proximityTargeting.add; remove by criterionId via proximityTargeting.remove (get criterionIds from getCampaignSettings or runScript).
| Name | Required | Description | Default |
|---|---|---|---|
| networks | No | Network targeting toggles — only specified fields are changed | |
| accountId | No | Account ID (omit for primary) | |
| adSchedule | No | Ad schedule (dayparting) — REPLACES the entire current schedule. For smart-bidding campaigns, non-24/7 schedules can reduce learning signal; the tool returns a SMART_BIDDING_SCHEDULE_RESTRICTION warning when detected. | |
| campaignId | Yes | ||
| locationTargeting | No | Positive location targeting — where ads should show | |
| proximityTargeting | No | Radius-based proximity targeting — target people within N miles/km of a lat/lng point. | |
| negativeGeoTargetType | No | Who is excluded based on excluded locations. PRESENCE: exclude people physically there. PRESENCE_OR_INTEREST: exclude people in or interested in the excluded area. | |
| positiveGeoTargetType | No | Who sees ads based on location intent. PRESENCE: only people physically in the targeted area. PRESENCE_OR_INTEREST: people in OR interested in the area (Google default). Use PRESENCE for purely local intent; use PRESENCE_OR_INTEREST for broader reach. | |
| negativeLocationTargeting | No | Negative location targeting — where ads should NOT show | |
| acknowledgeExperimentImpact | No | Danger override. Set true only after the user explicitly accepts that this mutation touches a campaign in an active experiment, or after applying the same intended change to both arms. |
updateConversionActionIdempotentInspect
Update an existing conversion action's settings — promote secondary to primary, change value, rename, fix currency. Conversion actions imported from GA4/UA/Floodlight/Firebase/Salesforce/Search Ads 360, Smart Campaign auto-actions, Store Visits, app-store actions, local_services_* / Local Services Ads actions, and manager-inherited actions are read-only via the API — the update call will be rejected locally before reaching Google. To check before calling: read conversion_action.type and conversion_action.owner_customer via runScript (e.g. await ads.gaql(ads.queries.conversionActions)) or write a direct FROM conversion_action query. LSA conversion names may appear in segments.conversion_action_name without appearing as mutable FROM conversion_action rows. To delete a conversion action, use removeConversionAction (status=REMOVED is not accepted by Google for updates). Returns changeId.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | ||
| status | No | ENABLED = active. To delete, use removeConversionAction instead — Google rejects status=REMOVED on update. | |
| category | No | ||
| accountId | No | Account ID (omit for primary) | |
| countingType | No | ||
| currencyCode | No | ISO 4217 currency code (e.g. 'USD', 'EUR') for this action's conversion values. Use this to migrate legacy 'XXX' (unset) actions to a real currency so reporting can roll up. 'XXX' is rejected on writes. | |
| defaultValue | No | Default conversion value in account currency | |
| primaryForGoal | No | true = primary (included in Conversions column for bidding), false = secondary (observation only) | |
| conversionActionId | Yes | Conversion action ID (query conversion_action via runScript) | |
| alwaysUseDefaultValue | No | ||
| enhancedConversionsForLeads | No | Enable Enhanced Conversions for Leads at account level | |
| viewThroughLookbackWindowDays | No | ||
| clickThroughLookbackWindowDays | No |
uploadClickConversionsIdempotentInspect
Upload offline click conversions to Google Ads for attribution. Supports Enhanced Conversions for Leads via hashed email/phone matching. Each conversion needs a gclid OR hashed user identifiers. Max 2000 conversions per call. Partial failures are reported per-row.
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | No | Account ID (omit for primary) | |
| conversions | Yes | Conversions to upload (max 2000 per request) | |
| conversionActionId | Yes | Conversion action ID to attribute conversions to |
Claim this connector by publishing a /.well-known/glama.json file on your server's domain with the following structure:
{
"$schema": "https://glama.ai/mcp/schemas/connector.json",
"maintainers": [{ "email": "your-email@example.com" }]
}The email address must match the email associated with your Glama account. Once published, Glama will automatically detect and verify the file within a few minutes.
Control your server's listing on Glama, including description and metadata
Access analytics and receive server usage reports
Get monitoring and health status updates for your server
Feature your server to boost visibility and reach more users
For users:
Full audit trail – every tool call is logged with inputs and outputs for compliance and debugging
Granular tool control – enable or disable individual tools per connector to limit what your AI agents can do
Centralized credential management – store and rotate API keys and OAuth tokens in one place
Change alerts – get notified when a connector changes its schema, adds or removes tools, or updates tool definitions, so nothing breaks silently
For server owners:
Proven adoption – public usage metrics on your listing show real-world traction and build trust with prospective users
Tool-level analytics – see which tools are being used most, helping you prioritize development and documentation
Direct user feedback – users can report issues and suggest improvements through the listing, giving you a channel you would not have otherwise
The connector status is unhealthy when Glama is unable to successfully connect to the server. This can happen for several reasons:
The server is experiencing an outage
The URL of the server is wrong
Credentials required to access the server are missing or invalid
If you are the owner of this MCP connector and would like to make modifications to the listing, including providing test credentials for accessing the server, please contact support@glama.ai.
Discussions
No comments yet. Be the first to start the discussion!