{
  "id": "2RcO8eSL2bfsn9OLQmdrA",
  "name": "Google Ads Campaign Performance Audit",
  "tags": [],
  "nodes": [
    {
      "id": "8220c553-5f0a-43f3-8c05-6d789ac62bb7",
      "name": "overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        -320
      ],
      "parameters": {
        "width": 904,
        "height": 336,
        "content": "## \ud83d\udcca Google Ads Campaign Performance Audit\n\nPulls a complete **read-only** audit of one Google Ads account and merges everything into a single JSON payload: campaigns, ad groups, keywords, search terms, ads, negative keywords, audience segments and extensions/assets.\n\n### How it works\n1. **Setup & trigger** - the `.env` node holds your token, customer ID, date range and API version. A short `Wait` paces the calls.\n2. **Pull reports** - 8 parallel HTTP requests query the Google Ads `searchStream` API (`v23` tested).\n3. **Merge & output** - `Merge All Outputs` returns one keyed JSON object with all eight report sets.\n\n### Requirements\n- Google Ads **OAuth2** credential with access to the target customer ID\n- A Google Ads **developer token**\n\n\ud83d\udc49 Fill in the **Setup** note (bottom-left) before running."
      },
      "typeVersion": 1
    },
    {
      "id": "216b4127-6eef-487e-b917-92f5cc829a21",
      "name": "sec_setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        48
      ],
      "parameters": {
        "color": 4,
        "width": 480,
        "height": 320,
        "content": "## 1 \u00b7 Setup & trigger"
      },
      "typeVersion": 1
    },
    {
      "id": "1efa5317-ce8a-43a6-bfbc-16a84e198ca4",
      "name": "sec_pull",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1024,
        48
      ],
      "parameters": {
        "color": 5,
        "width": 896,
        "height": 544,
        "content": "## 2 \u00b7 Pull Google Ads reports (parallel)"
      },
      "typeVersion": 1
    },
    {
      "id": "55da01fd-522e-4153-86c9-0587ccec01b7",
      "name": "sec_merge",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1936,
        272
      ],
      "parameters": {
        "color": 7,
        "width": 256,
        "height": 320,
        "content": "## 3 \u00b7 Merge & output"
      },
      "typeVersion": 1
    },
    {
      "id": "24e6d456-12bf-4c8e-9e1d-78384cba79bc",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        560,
        176
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "3f42e845-7dad-42ae-a099-9c8d7c1f9488",
      "name": "Merge All Outputs",
      "type": "n8n-nodes-base.code",
      "position": [
        2000,
        400
      ],
      "parameters": {
        "jsCode": "\nconst nodeNames = [\n  '1. Campaigns',\n  '2. Ad Groups', \n  '3. Keywords',\n  '4. Search Terms',\n  '5. Ads',\n  '6. Negative Keywords',\n  '7. Audience Segments',\n  '8. Extensions & Assets'\n];\n\nconst output = {};\n\nfor (const name of nodeNames) {\n  try {\n    const items = $(''+name).all();\n    const key = name.replace(/^\\d+\\.\\s*/, '').toLowerCase().replace(/[^a-z0-9]+/g, '_');\n    output[key] = items.map(i => i.json);\n  } catch(e) {\n    output[name] = { error: e.message };\n  }\n}\n\nreturn [{ json: output }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "a10f8631-5f4b-4dd7-9616-0a604c26a761",
      "name": "1. Campaigns",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1072,
        176
      ],
      "parameters": {
        "url": "=https://googleads.googleapis.com/{{ $json.api_version }}/customers/{{$('.env').item.json.account}}/googleAds:searchStream",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ query: \"SELECT campaign.id, campaign.name, campaign.status, campaign.advertising_channel_type, campaign.bidding_strategy_type, campaign.target_cpa.target_cpa_micros, campaign.target_roas.target_roas, campaign.maximize_conversions.target_cpa_micros, campaign.maximize_conversion_value.target_roas, campaign.serving_status, campaign.optimization_score, metrics.cost_micros, metrics.clicks, metrics.impressions, metrics.conversions, metrics.conversions_value, metrics.ctr, metrics.average_cpc, metrics.search_impression_share, metrics.search_budget_lost_impression_share, metrics.search_rank_lost_impression_share FROM campaign WHERE segments.date BETWEEN '\" + $('.env').item.json.DATE_FROM + \"' AND '\" + $('.env').item.json.DATE_TO + \"' AND campaign.status != 'REMOVED'\" }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "developer-token",
              "value": "={{$('.env').item.json.GOOGLE_ADS_DEVELOPER_TOKEN}}"
            }
          ]
        },
        "nodeCredentialType": "googleAdsOAuth2Api"
      },
      "credentials": {
        "googleAdsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "0201b16e-9e9b-4bcd-b70f-9371c0e68d88",
      "name": "2. Ad Groups",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1296,
        176
      ],
      "parameters": {
        "url": "=https://googleads.googleapis.com/{{ $('.env').item.json.api_version }}/customers/{{$('.env').item.json.account}}/googleAds:searchStream",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ query: \"SELECT campaign.id, campaign.name, ad_group.id, ad_group.name, ad_group.status, ad_group.type, ad_group.cpc_bid_micros, metrics.cost_micros, metrics.clicks, metrics.impressions, metrics.conversions, metrics.ctr, metrics.average_cpc FROM ad_group WHERE segments.date BETWEEN '\" + $('.env').item.json.DATE_FROM + \"' AND '\" + $('.env').item.json.DATE_TO + \"' AND campaign.status != 'REMOVED' AND ad_group.status != 'REMOVED'\" }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "developer-token",
              "value": "={{$('.env').item.json.GOOGLE_ADS_DEVELOPER_TOKEN}}"
            }
          ]
        },
        "nodeCredentialType": "googleAdsOAuth2Api"
      },
      "credentials": {
        "googleAdsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "fd26688b-856e-4229-9d9c-a50a0bdfd2f7",
      "name": "3. Keywords",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1520,
        176
      ],
      "parameters": {
        "url": "=https://googleads.googleapis.com/{{ $('.env').item.json.api_version }}/customers/{{$('.env').item.json.account}}/googleAds:searchStream",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ query: \"SELECT campaign.name, ad_group.name, ad_group_criterion.keyword.text, ad_group_criterion.keyword.match_type, ad_group_criterion.status, ad_group_criterion.quality_info.quality_score, ad_group_criterion.quality_info.creative_quality_score, ad_group_criterion.quality_info.post_click_quality_score, ad_group_criterion.quality_info.search_predicted_ctr, metrics.cost_micros, metrics.clicks, metrics.impressions, metrics.conversions, metrics.average_cpc FROM keyword_view WHERE segments.date BETWEEN '\" + $('.env').item.json.DATE_FROM + \"' AND '\" + $('.env').item.json.DATE_TO + \"' AND campaign.status != 'REMOVED' AND ad_group.status != 'REMOVED' AND ad_group_criterion.status != 'REMOVED'\" }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "developer-token",
              "value": "={{$('.env').item.json.GOOGLE_ADS_DEVELOPER_TOKEN}}"
            }
          ]
        },
        "nodeCredentialType": "googleAdsOAuth2Api"
      },
      "credentials": {
        "googleAdsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "ce590088-ce2c-4bbc-b78a-be2c1b836e77",
      "name": "4. Search Terms",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1744,
        176
      ],
      "parameters": {
        "url": "=https://googleads.googleapis.com/{{ $('.env').item.json.api_version }}/customers/{{$('.env').item.json.account}}/googleAds:searchStream",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ query: \"SELECT campaign.name, ad_group.name, search_term_view.search_term, search_term_view.status, metrics.cost_micros, metrics.clicks, metrics.impressions, metrics.conversions, metrics.ctr, metrics.average_cpc FROM search_term_view WHERE segments.date BETWEEN '\" + $('.env').item.json.DATE_FROM + \"' AND '\" + $('.env').item.json.DATE_TO + \"' AND campaign.status != 'REMOVED' ORDER BY metrics.cost_micros DESC LIMIT 300\" }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "developer-token",
              "value": "={{$('.env').item.json.GOOGLE_ADS_DEVELOPER_TOKEN}}"
            }
          ]
        },
        "nodeCredentialType": "googleAdsOAuth2Api"
      },
      "credentials": {
        "googleAdsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "641ed055-3614-48c5-b2f2-65684238d414",
      "name": "5. Ads",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1072,
        400
      ],
      "parameters": {
        "url": "=https://googleads.googleapis.com/{{ $('.env').item.json.api_version }}/customers/{{$('.env').item.json.account}}/googleAds:searchStream",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ query: \"SELECT campaign.name, ad_group.name, ad_group_ad.ad.id, ad_group_ad.ad.type, ad_group_ad.status, ad_group_ad.ad.responsive_search_ad.headlines, ad_group_ad.ad.responsive_search_ad.descriptions, ad_group_ad.ad.final_urls, ad_group_ad.policy_summary.approval_status, metrics.cost_micros, metrics.clicks, metrics.impressions, metrics.conversions, metrics.ctr, metrics.average_cpc FROM ad_group_ad WHERE segments.date BETWEEN '\" + $('.env').item.json.DATE_FROM + \"' AND '\" + $('.env').item.json.DATE_TO + \"' AND campaign.status != 'REMOVED' AND ad_group_ad.status != 'REMOVED'\" }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "developer-token",
              "value": "={{$('.env').item.json.GOOGLE_ADS_DEVELOPER_TOKEN}}"
            }
          ]
        },
        "nodeCredentialType": "googleAdsOAuth2Api"
      },
      "credentials": {
        "googleAdsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "aa959b9a-1a84-4792-8d7a-148f651e78b2",
      "name": "6. Negative Keywords",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1296,
        400
      ],
      "parameters": {
        "url": "=https://googleads.googleapis.com/{{ $('.env').item.json.api_version }}/customers/{{$('.env').item.json.account}}/googleAds:searchStream",
        "method": "POST",
        "options": {},
        "jsonBody": "{ \"query\": \"SELECT campaign.name, campaign_criterion.keyword.text, campaign_criterion.keyword.match_type, campaign_criterion.negative FROM campaign_criterion WHERE campaign_criterion.negative = TRUE AND campaign.status != 'REMOVED'\" }",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "developer-token",
              "value": "={{$('.env').item.json.GOOGLE_ADS_DEVELOPER_TOKEN}}"
            }
          ]
        },
        "nodeCredentialType": "googleAdsOAuth2Api"
      },
      "credentials": {
        "googleAdsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "ae3347f6-23c6-46ac-84a7-27b44d7b6dd5",
      "name": "7. Audience Segments",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1520,
        400
      ],
      "parameters": {
        "url": "=https://googleads.googleapis.com/{{ $('.env').item.json.api_version }}/customers/{{$('.env').item.json.account}}/googleAds:searchStream",
        "method": "POST",
        "options": {},
        "jsonBody": "{ \"query\": \"SELECT campaign.name, ad_group.name, ad_group_criterion.type, ad_group_criterion.bid_modifier, ad_group_criterion.status FROM ad_group_criterion WHERE campaign.status != 'REMOVED' AND ad_group_criterion.status != 'REMOVED'\" }",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "developer-token",
              "value": "={{$('.env').item.json.GOOGLE_ADS_DEVELOPER_TOKEN}}"
            }
          ]
        },
        "nodeCredentialType": "googleAdsOAuth2Api"
      },
      "credentials": {
        "googleAdsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "1a0d082a-6a11-4b50-9c2a-371ebffb1785",
      "name": "8. Extensions & Assets",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1744,
        400
      ],
      "parameters": {
        "url": "=https://googleads.googleapis.com/{{ $('.env').item.json.api_version }}/customers/{{$('.env').item.json.account}}/googleAds:searchStream",
        "method": "POST",
        "options": {},
        "jsonBody": "{ \"query\": \"SELECT campaign.name, campaign.id, campaign.status, asset.type, asset.name, asset.call_asset.phone_number, asset.callout_asset.callout_text, asset.sitelink_asset.link_text FROM campaign_asset WHERE campaign.status != 'REMOVED'\" }",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "developer-token",
              "value": "={{$('.env').item.json.GOOGLE_ADS_DEVELOPER_TOKEN}}"
            }
          ]
        },
        "nodeCredentialType": "googleAdsOAuth2Api"
      },
      "credentials": {
        "googleAdsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "781419b1-329d-4794-9e7f-507c81624d92",
      "name": ".env",
      "type": "n8n-nodes-base.set",
      "position": [
        704,
        176
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "3966502b-d96a-4caf-a610-d0b6ffe109a5",
              "name": "GOOGLE_ADS_DEVELOPER_TOKEN",
              "type": "string",
              "value": ""
            },
            {
              "id": "083267bb-e5d4-4742-9196-d6f8eab298a1",
              "name": "account",
              "type": "string",
              "value": ""
            },
            {
              "id": "fa24102d-a160-4e04-84fb-7a2c9dd6b771",
              "name": "DATE_FROM",
              "type": "string",
              "value": "=2026-04-16"
            },
            {
              "id": "f0c3bce2-9043-46c5-a177-b7ca441015b9",
              "name": "DATE_TO",
              "type": "string",
              "value": "=2026-06-12"
            },
            {
              "id": "3d33ac17-b55d-44ec-b454-78b399b3cd85",
              "name": "api_version",
              "type": "string",
              "value": "v23"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "7d0d3c6b-d53a-4ad9-aa44-ed2910c45845",
      "name": "Wait",
      "type": "n8n-nodes-base.wait",
      "position": [
        848,
        176
      ],
      "parameters": {
        "amount": 2
      },
      "typeVersion": 1.1
    },
    {
      "id": "5671a3bd-93d9-45e6-87c5-e91a956af8c1",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        640
      ],
      "parameters": {
        "color": 6,
        "width": 480,
        "height": 288,
        "content": "### Google Ads - Account Audit\n#### Setup - open `.env` and fill in before running\n`GOOGLE_ADS_DEVELOPER_TOKEN`\n\u2192 Google Ads Manager \u2192 Tools \u2192 API Centre \u2192 View token\n`GOOGLE_ADS_CUSTOMER_ID`\n\u2192 Sub-account ID, no dashes (e.g. 3996289339)\n`DATE_FROM` / `DATE_TO`\n\u2192 See date examples\n`api_version` \n\u2192 enter latest Google API version - `v23` tested"
      },
      "typeVersion": 1
    },
    {
      "id": "379a373a-e80c-4801-93cb-0de2b489602b",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1024,
        640
      ],
      "parameters": {
        "color": 6,
        "width": 1168,
        "height": 288,
        "content": "### Date examples\n**Fixed:** `2026-05-01` / `2026-05-31`\n**Last month:** `{{ DateTime.now().minus({months:1}).startOf('month').toFormat('yyyy-MM-dd') }}` / `{{ DateTime.now().minus({months:1}).endOf('month').toFormat('yyyy-MM-dd') }}`\n**Last week:** `{{ DateTime.now().minus({weeks: 1}).startOf('week').toFormat('yyyy-MM-dd') }}` / `{{ DateTime.now().minus({weeks: 1}).endOf('week').toFormat('yyyy-MM-dd') }}`\n**Last 30 days:** `{{ DateTime.now().minus({days:30}).toFormat('yyyy-MM-dd') }}` / `{{ DateTime.now().minus({days:1}).toFormat('yyyy-MM-dd') }}`\n### Credentials\nConnect Google Ads OAuth2 credential to any HTTP node on import. Account must have access to the customer ID (`GOOGLE_ADS_CUSTOMER_ID`).\n### Output\n**Merge All Outputs** node returns single JSON with keys: `campaigns` `ad_groups` `keywords` `search_terms` `ads` `negative_keywords` `audience_segments` `extensions_assets`"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "6fdff922-7748-45df-888b-e1c06a15bd55",
  "connections": {
    ".env": {
      "main": [
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait": {
      "main": [
        [
          {
            "node": "1. Campaigns",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. Ads": {
      "main": [
        [
          {
            "node": "6. Negative Keywords",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Keywords": {
      "main": [
        [
          {
            "node": "4. Search Terms",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Campaigns": {
      "main": [
        [
          {
            "node": "2. Ad Groups",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Ad Groups": {
      "main": [
        [
          {
            "node": "3. Keywords",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Search Terms": {
      "main": [
        [
          {
            "node": "5. Ads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6. Negative Keywords": {
      "main": [
        [
          {
            "node": "7. Audience Segments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7. Audience Segments": {
      "main": [
        [
          {
            "node": "8. Extensions & Assets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "8. Extensions & Assets": {
      "main": [
        [
          {
            "node": "Merge All Outputs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": ".env",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}